App下載

詳解Java并發(fā)編程中怎么實(shí)現(xiàn)線程之間的共享和協(xié)作

紓寒 2021-08-09 14:58:50 瀏覽數(shù) (1696)
反饋

一、線程間的共享

1.1 ynchronized內(nèi)置鎖

用處

  • Java支持多個(gè)線程同時(shí)訪問(wèn)一個(gè)對(duì)象或者對(duì)象的成員變量
  • 關(guān)鍵字synchronized可以修飾方法或者以同步塊的形式來(lái)進(jìn)行使用
  • 它主要確保多個(gè)線程在同一個(gè)時(shí)刻,只能有一個(gè)線程處于方法或者同步塊中
  • 它保證了線程對(duì)變量訪問(wèn)的可見性和排他性(原子性、可見性、有序性),又稱為內(nèi)置鎖機(jī)制。

對(duì)象鎖和類鎖

  • 對(duì)象鎖是用于對(duì)象實(shí)例方法,或者一個(gè)對(duì)象實(shí)例上的
  • 類鎖是用于類的靜態(tài)方法或者一個(gè)類的class對(duì)象上的
  • 類的對(duì)象實(shí)例可以有很多個(gè),但是每個(gè)類只有一個(gè)class對(duì)象,所以不同對(duì)象實(shí)例的對(duì)象鎖是互不干擾的,但是每個(gè)類只有一個(gè)類鎖
  • 注意的是,其實(shí)類鎖只是一個(gè)概念上的東西,并不是真實(shí)存在的,類鎖其實(shí)鎖的是每個(gè)類的對(duì)應(yīng)的class對(duì)象
  • 類鎖和對(duì)象鎖之間也是互不干擾的。

1.2 volatile關(guān)鍵字

  • 最輕量的同步機(jī)制,保證可見性,不保證原子性
  • volatile保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見的。
  • volatile最適用的場(chǎng)景:只有一線程寫,多個(gè)線程讀的場(chǎng)景

1.3 ThreadLocal

  • ThreadLocal 和 Synchonized 都用于解決多線程并發(fā)訪問(wèn)。
  • 可是ThreadLocal與synchronized有本質(zhì)的差別:
  • synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該僅僅能被一個(gè)線程訪問(wèn)。
  • 而ThreadLocal為每個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問(wèn)到的并非同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。
  • Spring的事務(wù)就借助了ThreadLocal類。

1.4 Spring的事務(wù)借助ThreadLocal類

Spring會(huì)從數(shù)據(jù)庫(kù)連接池中獲得一個(gè)connection,然會(huì)把connection放進(jìn)ThreadLocal中,也就和線程綁定了,事務(wù)需要提交或者回滾,只要從ThreadLocal中拿到connection進(jìn)行操作。

1.4.1 為何Spring的事務(wù)要借助ThreadLocal類?

以JDBC為例,正常的事務(wù)代碼可能如下:
dbc = new DataBaseConnection();//第1行
Connection con = dbc.getConnection();//第2行
con.setAutoCommit(false);// //第3行
con.executeUpdate(...);//第4行
con.executeUpdate(...);//第5行
con.executeUpdate(...);//第6行
con.commit();第7行
上述代碼,可以分成三個(gè)部分:
事務(wù)準(zhǔn)備階段:第1~3行
業(yè)務(wù)處理階段:第4~6行
事務(wù)提交階段:第7行 
  • 不管我們開啟事務(wù)還是執(zhí)行具體的sql都需要一個(gè)具體的數(shù)據(jù)庫(kù)連接。
  • 開發(fā)應(yīng)用一般都采用三層結(jié)構(gòu),我們的Service會(huì)調(diào)用一系列的DAO對(duì)數(shù)據(jù)庫(kù)進(jìn)行多次操作,那么,這個(gè)時(shí)候我們就無(wú)法控制事務(wù)的邊界了,因?yàn)閷?shí)際應(yīng)用當(dāng)中,我們的Service調(diào)用的DAO的個(gè)數(shù)是不確定的,可根據(jù)需求而變化,而且還可能出現(xiàn)Service調(diào)用Service的情況。
  • 如果不使用ThreadLocal,如何讓三個(gè)DAO使用同一個(gè)數(shù)據(jù)源連接呢?我們就必須為每個(gè)DAO傳遞同一個(gè)數(shù)據(jù)庫(kù)連接,要么就是在DAO實(shí)例化的時(shí)候作為構(gòu)造方法的參數(shù)傳遞,要么在每個(gè)DAO的實(shí)例方法中作為方法的參數(shù)傳遞。
Connection conn = getConnection();
Dao1 dao1 = new Dao1(conn);
dao1.exec();
Dao2 dao2 = new Dao2(conn);
dao2.exec();
Dao3 dao3 = new Dao3(conn);
dao3.exec();
conn.commit();
  • 為了讓這個(gè)數(shù)據(jù)庫(kù)連接可以跨階段傳遞,又不顯式的進(jìn)行參數(shù)傳遞,就必須使用別的辦法。
  • Web容器中,每個(gè)完整的請(qǐng)求周期會(huì)由一個(gè)線程來(lái)處理。因此,如果我們能將一些參數(shù)綁定到線程的話,就可以實(shí)現(xiàn)在軟件架構(gòu)中跨層次的參數(shù)共享(是隱式的共享)。而JAVA中恰好提供了綁定的方法–使用ThreadLocal。
  • 結(jié)合使用Spring里的IOC和AOP,就可以很好的解決這一點(diǎn)。
  • 只要將一個(gè)數(shù)據(jù)庫(kù)連接放入ThreadLocal中,當(dāng)前線程執(zhí)行時(shí)只要有使用數(shù)據(jù)庫(kù)連接的地方就從ThreadLocal獲得就行了。

1.4.2 ThreadLocal的使用

void set(Object value)

  • 設(shè)置當(dāng)前線程的線程局部變量的值。

public Object get()

  • 該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量。

public void remove()

  • 將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。
  • 需要指出的是,當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收
  • 所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。

protected Object initialValue()

  • 返回該線程局部變量的初始值
  • 該方法是一個(gè)protected的方法,顯然是為了讓子類覆蓋而設(shè)計(jì)的。
  • 這個(gè)方法是一個(gè)延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。
  • ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null。

public final static ThreadLocal RESOURCE = new ThreadLocal()

  • RESOURCE代表一個(gè)能夠存放String類型的ThreadLocal對(duì)象。
  • 此時(shí)不論任何一個(gè)線程能夠并發(fā)訪問(wèn)這個(gè)變量,對(duì)它進(jìn)行寫入、讀取操作,都是線程安全的。

1.4.3 ThreadLocal實(shí)現(xiàn)解析

2.ThreadLocal原理

public class ThreadLocal<T> {
    //get方法,其實(shí)就是拿到每個(gè)線程獨(dú)有的ThreadLocalMap
    //然后再用ThreadLocal的當(dāng)前實(shí)例,拿到Map中的相應(yīng)的Entry,然后就可以拿到相應(yīng)的值返回出去。
    //如果Map為空,還會(huì)先進(jìn)行map的創(chuàng)建,初始化等工作。
    public T get() {
        //先取到當(dāng)前線程,然后調(diào)用getMap方法獲取對(duì)應(yīng)線程的ThreadLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    // Thread類中有一個(gè) ThreadLocalMap 類型成員,所以getMap是直接返回Thread的成員
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類
    static class ThreadLocalMap {
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 用數(shù)組保存 Entry , 因?yàn)榭赡苡卸鄠€(gè)變量需要線程隔離訪問(wèn),即聲明多個(gè) ThreadLocal 變量
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // Entry 類似于 map 的 key-value 結(jié)構(gòu)
            // key 就是 ThreadLocal, value 就是需要隔離訪問(wèn)的變量
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        ...
    }
    
    //Entry內(nèi)部靜態(tài)類,它繼承了WeakReference,
    //總之它記錄了兩個(gè)信息,一個(gè)是ThreadLocal<?>類型,一個(gè)是Object類型的值
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    //getEntry方法則是獲取某個(gè)ThreadLocal對(duì)應(yīng)的值
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
    
    //set方法就是更新或賦值相應(yīng)的ThreadLocal對(duì)應(yīng)的值
    private void set(ThreadLocal<?> key, Object value) {
        ...
    }
    ...
}

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ...
}

1.5引用基礎(chǔ)知識(shí)

1.5.1 引用

  • 創(chuàng)建對(duì)象 Object o = new Object();
  • 這個(gè)o,我們可以稱之為對(duì)象引用,而new Object()我們可以稱之為在內(nèi)存中產(chǎn)生了一個(gè)對(duì)象實(shí)例。
  • 當(dāng) o=null 時(shí),只是表示o不再指向堆中Object的對(duì)象實(shí)例,不代表這個(gè)對(duì)象實(shí)例不存在了。

1.5.2 強(qiáng)引用

  • 指在程序代碼之中普遍存在的,類似“Object obj=new Object()
  • 這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象實(shí)例。

1.5.3 軟引用

  •  用來(lái)描述一些還有用但并非必需的對(duì)象。
  • 對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象實(shí)例列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。
  • 在JDK 1.2之后,提供了SoftReference類來(lái)實(shí)現(xiàn)軟引用。

1.5.4 弱引用

  • 用來(lái)描述非必需對(duì)象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象實(shí)例只能生存到下一次垃圾收集發(fā)生之前。
  • 當(dāng)垃圾收集器工作時(shí),無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象實(shí)例。
  • 在JDK 1.2之后,提供了WeakReference類來(lái)實(shí)現(xiàn)弱引用。

1.5.5 虛引用

  •  也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。
  • 一個(gè)對(duì)象實(shí)例是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例。
  • 為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象實(shí)例被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。
  • 在JDK 1.2之后,提供了PhantomReference類來(lái)實(shí)現(xiàn)虛引用。

1.6 使用 ThreadLocal 引發(fā)內(nèi)存泄漏

1.6.1 準(zhǔn)備

將堆內(nèi)存大小設(shè)置為-Xmx256m

啟用一個(gè)線程池,大小固定為5個(gè)線程

//5M大小的數(shù)組
private static class LocalVariable {
    private byte[] value = new byte[1024*1024*5];
}

// 創(chuàng)建線程池,固定為5個(gè)線程
private static ThreadPoolExecutor poolExecutor
        = new ThreadPoolExecutor(5,5,1, TimeUnit.MINUTES,new LinkedBlockingQueue<>());
        
//ThreadLocal共享變量
private ThreadLocal<LocalVariable> data;

@Override
public void run() {
    //場(chǎng)景1:不執(zhí)行任何有意義的代碼,當(dāng)所有的任務(wù)提交執(zhí)行完成后,查看內(nèi)存占用情況,占用 25M 左右
    //System.out.println("hello ThreadLocal...");

    //場(chǎng)景2:創(chuàng)建 數(shù)據(jù)對(duì)象,執(zhí)行完成后,查看內(nèi)存占用情況,與場(chǎng)景1相同
    //new LocalVariable();

    //場(chǎng)景3:?jiǎn)⒂?ThreadLocal,執(zhí)行完成后,查看內(nèi)存占用情況,占用 100M 左右
    ThreadLocalOOM obj = new ThreadLocalOOM();
    obj.data = new ThreadLocal<>();
    obj.data.set(new LocalVariable());
    System.out.println("update ThreadLocal data value..........");

    //場(chǎng)景4: 加入 remove(),執(zhí)行完成后,查看內(nèi)存占用情況,與場(chǎng)景1相同
    //obj.data.remove();

    //分析:在場(chǎng)景3中,當(dāng)啟用了ThreadLocal以后確實(shí)發(fā)生了內(nèi)存泄漏
}

場(chǎng)景1:

  • 首先任務(wù)中不執(zhí)行任何有意義的代碼,當(dāng)所有的任務(wù)提交執(zhí)行完成后,可以看見,我們這個(gè)應(yīng)用的內(nèi)存占用基本上為25M左右

場(chǎng)景2:

  • 然后我們只簡(jiǎn)單的在每個(gè)任務(wù)中new出一個(gè)數(shù)組,執(zhí)行完成后我們可以看見,內(nèi)存占用基本和場(chǎng)景1相同

場(chǎng)景3:

  • 當(dāng)我們啟用了ThreadLocal以后,執(zhí)行完成后我們可以看見,內(nèi)存占用變?yōu)榱?00多M

場(chǎng)景4:

  • 我們加入一行代碼 obj.data.remove(); ,再執(zhí)行,看看內(nèi)存情況,可以看見,內(nèi)存占用基本和場(chǎng)景1相同。

場(chǎng)景分析:

  • 這就充分說(shuō)明,場(chǎng)景3,當(dāng)我們啟用了ThreadLocal以后確實(shí)發(fā)生了內(nèi)存泄漏。

1.6.2 內(nèi)存泄漏分析

  •  通過(guò)對(duì)ThreadLocal的分析,我們可以知道每個(gè)Thread 維護(hù)一個(gè) ThreadLocalMap,這個(gè)映射表的 key 是 ThreadLocal實(shí)例本身,value 是真正需要存儲(chǔ)的 Object,也就是說(shuō) ThreadLocal 本身并不存儲(chǔ)值,它只是作為一個(gè) key 來(lái)讓線程從 ThreadLocalMap 獲取 value。
  • 仔細(xì)觀察ThreadLocalMap,這個(gè)map是使用 ThreadLocal 的弱引用作為 Key 的,弱引用的對(duì)象在 GC 時(shí)會(huì)被回收。

2.ThreadLocal原理

  • 圖中的虛線表示弱引用。
  • 當(dāng)把threadlocal變量置為null以后,沒(méi)有任何強(qiáng)引用指向threadlocal實(shí)例,所以threadlocal將會(huì)被gc回收
  • 這樣一來(lái),ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒(méi)有辦法訪問(wèn)這些key為null的Entry的value
  • 如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而這塊value永遠(yuǎn)不會(huì)被訪問(wèn)到了,所以存在著內(nèi)存泄露。
  • 可以通過(guò)Debug模式,查看變量 poolExecutor->workers->0->thread->threadLocals,會(huì)發(fā)現(xiàn)線程的成員變量 threadLocals 的 size=1,map 中存放了一個(gè) referent=null, value=data對(duì)象
  • 只有當(dāng)前thread結(jié)束以后,current thread就不會(huì)存在棧中,強(qiáng)引用斷開,Current Thread、Map value將全部被GC回收。
  • 最好的做法是在不需要使用ThreadLocal變量后,都調(diào)用它的remove()方法,清除數(shù)據(jù)。

場(chǎng)景3分析:

  • 在場(chǎng)景3中,雖然線程池里面的任務(wù)執(zhí)行完畢了,但是線程池里面的5個(gè)線程會(huì)一直存在直到JVM退出,我們set了線程的localVariable變量后沒(méi)有調(diào)用localVariable.remove()方法,導(dǎo)致線程池里面的5個(gè)線程的threadLocals變量里面的new LocalVariable()實(shí)例沒(méi)有被釋放。

從表面上看內(nèi)存泄漏的根源在于使用了弱引用。為什么使用弱引用而不是強(qiáng)引用?下面我們分兩種情況討論:

  •  key 使用強(qiáng)引用:對(duì)ThreadLocal對(duì)象實(shí)例的引用被置為null了,但是ThreadLocalMap還持有這個(gè)ThreadLocal對(duì)象實(shí)例的強(qiáng)引用,如果沒(méi)有手動(dòng)刪除,ThreadLocal的對(duì)象實(shí)例不會(huì)被回收,導(dǎo)致Entry內(nèi)存泄漏。
  • key 使用弱引用:對(duì)ThreadLocal對(duì)象實(shí)例的引用被被置為null了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒(méi)有手動(dòng)刪除,ThreadLocal的對(duì)象實(shí)例也會(huì)被回收。value在下一次ThreadLocalMap調(diào)用set,get,remove都有機(jī)會(huì)被回收。
  • 比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果都沒(méi)有手動(dòng)刪除對(duì)應(yīng)key,都會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障。

因此,ThreadLocal內(nèi)存泄漏的根源是:

  • 由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果沒(méi)有手動(dòng)刪除對(duì)應(yīng)key就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/li>

總結(jié):

  • JVM利用設(shè)置ThreadLocalMap的Key為弱引用,來(lái)避免內(nèi)存泄露。
  • JVM利用調(diào)用remove、get、set方法的時(shí)候,回收弱引用。
  • 當(dāng)ThreadLocal存儲(chǔ)很多Key為null的Entry的時(shí)候,而不再去調(diào)用remove、get、set方法,那么將導(dǎo)致內(nèi)存泄漏。
  • 使用線程池 + ThreadLocal時(shí)要小心,因?yàn)檫@種情況下,線程是一直在不斷的重復(fù)運(yùn)行的,從而也就造成了value可能造成累積的情況。

錯(cuò)誤使用ThreadLocal導(dǎo)致線程不安全:

  • 仔細(xì)考察ThreadLocal和Thead的代碼,我們發(fā)現(xiàn)ThreadLocalMap中保存的其實(shí)是對(duì)象的一個(gè)引用,這樣的話,當(dāng)有其他線程對(duì)這個(gè)引用指向的對(duì)象實(shí)例做修改時(shí),其實(shí)也同時(shí)影響了所有的線程持有的對(duì)象引用所指向的同一個(gè)對(duì)象實(shí)例。
  • 這也就是為什么上面的程序?yàn)槭裁磿?huì)輸出一樣的結(jié)果:5個(gè)線程中保存的是同一個(gè)Number對(duì)象的引用,因此它們最終輸出的結(jié)果是相同的。
  • 正確的用法是讓每個(gè)線程中的ThreadLocal都應(yīng)該持有一個(gè)新的Number對(duì)象。 線程間的協(xié)作

二、線程間的協(xié)作

  • 線程之間相互配合,完成某項(xiàng)工作;
  • 比如一個(gè)線程修改了一個(gè)對(duì)象的值,而另一個(gè)線程感知到了變化,然后進(jìn)行相應(yīng)的操作;
  • 前者是生產(chǎn)者,后者就是消費(fèi)者,這種模式隔離了“做什么”(what)和“怎么做”(How);
  • 常見的方法是讓消費(fèi)者線程不斷地循環(huán)檢查變量是否符合預(yù)期在while循環(huán)中設(shè)置不滿足的條件,如果條件滿足則退出while循環(huán),從而完成消費(fèi)者的工作。

存在如下問(wèn)題:

  • 1)難以確保及時(shí)性;
  • 2)難以降低開銷。如果降低睡眠的時(shí)間,比如休眠1毫秒,這樣消費(fèi)者能更加迅速地發(fā)現(xiàn)條件變化,但是卻可能消耗更多的處理器資源,造成了無(wú)端的浪費(fèi)。

2.1等待和通知機(jī)制

是指一個(gè)線程A調(diào)用了對(duì)象O的wait()方法進(jìn)入等待狀態(tài),而另一個(gè)線程B調(diào)用了對(duì)象O的notify()或者notifyAll()方法,線程A收到通知后從對(duì)象O的wait()方法返回,進(jìn)而執(zhí)行后續(xù)操作。

上述兩個(gè)線程通過(guò)對(duì)象O來(lái)完成交互,而對(duì)象上的wait()和notify/notifyAll()的關(guān)系就如同開關(guān)信號(hào)一樣,用來(lái)完成等待方和通知方之間的交互工作。

notify():

通知一個(gè)在對(duì)象上等待的線程,使其從wait方法返回,而返回的前提是該線程獲取到了對(duì)象的鎖,沒(méi)有獲得鎖的線程重新進(jìn)入WAITING狀態(tài)。

notifyAll():

通知所有等待在該對(duì)象上的線程。

wait():

調(diào)用該方法的線程進(jìn)入 WAITING狀態(tài),只有等待另外線程的通知或被中斷才會(huì)返回.需要注意,調(diào)用wait()方法后,會(huì)釋放對(duì)象的鎖。

wait(long):

超時(shí)等待一段時(shí)間,這里的參數(shù)時(shí)間是毫秒,也就是等待長(zhǎng)達(dá)n毫秒,如果沒(méi)有通知就超時(shí)返回;

wait (long,int):

對(duì)于超時(shí)時(shí)間更細(xì)粒度的控制,可以達(dá)到納秒;

2.2等待和通知的標(biāo)準(zhǔn)范式

等待方遵循如下原則:

  •  1.獲取對(duì)象的鎖
  • 2.循環(huán)里判斷條件是否滿足,如果條件不滿足,那么調(diào)用對(duì)象的wait()方法,被通知后仍要檢查條件。
  • 條件滿足則執(zhí)行對(duì)應(yīng)的邏輯。
synchronized(對(duì)象){
    while(條件不滿足){
        對(duì)象.wait();
    }
    對(duì)應(yīng)的邏輯
}

通知方遵循如下原則:

  •  1.獲取對(duì)象的鎖。
  • 2.改變條件。
  • 3.通知所有等待在對(duì)象上的線程。
synchronized(對(duì)象){
    改變條件
    對(duì)象.notifyAll();
}

在調(diào)用wait()、notify()系列方法之前,線程必須要獲得該對(duì)象的對(duì)象級(jí)別鎖,即只能在同步方法或同步塊中調(diào)用wait() 方法、notify()系列方法;

  • 進(jìn)入wait() 方法后,當(dāng)前線程釋放鎖,在從wait() 返回前,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖,執(zhí)行notify()系列方法的線程退出synchronized代碼塊的時(shí)候后,他們就會(huì)去競(jìng)爭(zhēng)。
  • 如果其中一個(gè)線程獲得了該對(duì)象鎖,它就會(huì)繼續(xù)往下執(zhí)行,在它退出synchronized代碼塊,釋放鎖后,其他的已經(jīng)被喚醒的線程將會(huì)繼續(xù)競(jìng)爭(zhēng)獲取該鎖,一直進(jìn)行下去,直到所有被喚醒的線程都執(zhí)行完畢。

notify() 和 notifyAll() 應(yīng)該用誰(shuí)?

  • 盡量用 notifyAll()
  • 謹(jǐn)慎使用notify(),因?yàn)閚otify()只會(huì)喚醒一個(gè)線程,我們無(wú)法確保被喚醒的這個(gè)線程一定就是我們需要喚醒的線程;

2.3等待超時(shí)模式實(shí)現(xiàn)一個(gè)連接池

調(diào)用場(chǎng)景:

  •  調(diào)用一個(gè)方法時(shí)等待一段時(shí)間(一般來(lái)說(shuō)是給定一個(gè)時(shí)間段),如果該方法能夠在給定的時(shí)間段之內(nèi)得到結(jié)果,那么將結(jié)果立刻返回,反之,超時(shí)返回默認(rèn)結(jié)果。
  • 假設(shè)等待時(shí)間段是T,那么可以推斷出在當(dāng)前時(shí)間now+T之后就會(huì)超時(shí)
  • 等待持續(xù)時(shí)間:REMAINING=T ;
  • 超時(shí)時(shí)間:FUTURE=now+T ;
  • 客戶端獲取連接的過(guò)程被設(shè)定為等待超時(shí)的模式,也就是在1000毫秒內(nèi)如果無(wú)法獲取到可用連接,將會(huì)返回給客戶端一個(gè)null。
  • 設(shè)定連接池的大小為10個(gè),然后通過(guò)調(diào)節(jié)客戶端的線程數(shù)來(lái)模擬無(wú)法獲取連接的場(chǎng)景。
  • 通過(guò)構(gòu)造函數(shù)初始化連接的最大上限,通過(guò)一個(gè)雙向隊(duì)列來(lái)維護(hù)連接,調(diào)用方需要先調(diào)用fetchConnection(long)方法來(lái)指定在多少毫秒內(nèi)超時(shí)獲取連接,當(dāng)連接使用完成后,需要調(diào)用releaseConnection(Connection)方法將連接放回線程池

調(diào)用yield() 、sleep()、wait()、notify()等方法對(duì)鎖有何影響?

  • yield() 、sleep()被調(diào)用后,都不會(huì)釋放當(dāng)前線程所持有的鎖。
  • 調(diào)用wait()方法后,會(huì)釋放當(dāng)前線程持有的鎖,而且當(dāng)前被喚醒后,會(huì)重新去競(jìng)爭(zhēng)鎖,鎖競(jìng)爭(zhēng)到后才會(huì)執(zhí)行wait方法后面的代碼。
  • 調(diào)用notify()系列方法后,對(duì)鎖無(wú)影響,線程只有在synchronized同步代碼執(zhí)行完后才會(huì)自然而然的釋放鎖,所以notify()系列方法一般都是synchronized同步代碼的最后一行。

本篇文章關(guān)于Java并發(fā)編程中線程之間的共享和協(xié)作的詳細(xì)內(nèi)容就介紹結(jié)束了,想要了解更多相關(guān)Java并發(fā)編程和Java線程的知識(shí),搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章!也希望本篇文章能夠?qū)Υ蠹业膶W(xué)習(xí)和工作能夠有所幫助!


0 人點(diǎn)贊