一、線程間的共享
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)的可見(jiàn)性和排他性(原子性、可見(jiàn)性、有序性),又稱(chēng)為內(nèi)置鎖機(jī)制。
對(duì)象鎖和類(lèi)鎖
- 對(duì)象鎖是用于對(duì)象實(shí)例方法,或者一個(gè)對(duì)象實(shí)例上的
- 類(lèi)鎖是用于類(lèi)的靜態(tài)方法或者一個(gè)類(lèi)的class對(duì)象上的
- 類(lèi)的對(duì)象實(shí)例可以有很多個(gè),但是每個(gè)類(lèi)只有一個(gè)class對(duì)象,所以不同對(duì)象實(shí)例的對(duì)象鎖是互不干擾的,但是每個(gè)類(lèi)只有一個(gè)類(lèi)鎖
- 注意的是,其實(shí)類(lèi)鎖只是一個(gè)概念上的東西,并不是真實(shí)存在的,類(lèi)鎖其實(shí)鎖的是每個(gè)類(lèi)的對(duì)應(yīng)的class對(duì)象
- 類(lèi)鎖和對(duì)象鎖之間也是互不干擾的。
1.2 volatile關(guān)鍵字
- 最輕量的同步機(jī)制,保證可見(jiàn)性,不保證原子性
- volatile保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。
- volatile最適用的場(chǎng)景:只有一線程寫(xiě),多個(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類(lèi)。
1.4 Spring的事務(wù)借助ThreadLocal類(lèi)
Spring會(huì)從數(shù)據(jù)庫(kù)連接池中獲得一個(gè)connection,然會(huì)把connection放進(jìn)ThreadLocal中,也就和線程綁定了,事務(wù)需要提交或者回滾,只要從ThreadLocal中拿到connection進(jìn)行操作。
1.4.1 為何Spring的事務(wù)要借助ThreadLocal類(lèi)?
以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行
- 不管我們開(kāi)啟事務(wù)還是執(zhí)行具體的sql都需要一個(gè)具體的數(shù)據(jù)庫(kù)連接。
- 開(kāi)發(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的方法,顯然是為了讓子類(lèi)覆蓋而設(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類(lèi)型的ThreadLocal對(duì)象。
- 此時(shí)不論任何一個(gè)線程能夠并發(fā)訪問(wèn)這個(gè)變量,對(duì)它進(jìn)行寫(xiě)入、讀取操作,都是線程安全的。
1.4.3 ThreadLocal實(shí)現(xiàn)解析
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類(lèi)中有一個(gè) ThreadLocalMap 類(lèi)型成員,所以getMap是直接返回Thread的成員
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類(lè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 類(lèi)似于 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)類(lèi),它繼承了WeakReference,
//總之它記錄了兩個(gè)信息,一個(gè)是ThreadLocal<?>類(lèi)型,一個(gè)是Object類(lèi)型的值
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,我們可以稱(chēng)之為對(duì)象引用,而new Object()我們可以稱(chēng)之為在內(nèi)存中產(chǎn)生了一個(gè)對(duì)象實(shí)例。
- 當(dāng) o=null 時(shí),只是表示o不再指向堆中Object的對(duì)象實(shí)例,不代表這個(gè)對(duì)象實(shí)例不存在了。
1.5.2 強(qiáng)引用
- 指在程序代碼之中普遍存在的,類(lèi)似“Object obj=new Object()
- 這類(lèi)的引用,只要強(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)來(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)來(lái)實(shí)現(xiàn)弱引用。
1.5.5 虛引用
- 也稱(chēng)為幽靈引用或者幻影引用,它是最弱的一種引用關(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)來(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í)行完成后,可以看見(jiàn),我們這個(gè)應(yīng)用的內(nèi)存占用基本上為25M左右
場(chǎng)景2:
- 然后我們只簡(jiǎn)單的在每個(gè)任務(wù)中new出一個(gè)數(shù)組,執(zhí)行完成后我們可以看見(jiàn),內(nèi)存占用基本和場(chǎng)景1相同
場(chǎng)景3:
- 當(dāng)我們啟用了ThreadLocal以后,執(zhí)行完成后我們可以看見(jiàn),內(nèi)存占用變?yōu)榱?00多M
場(chǎng)景4:
- 我們加入一行代碼 obj.data.remove(); ,再執(zhí)行,看看內(nèi)存情況,可以看見(jiàn),內(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ì)被回收。
- 圖中的虛線表示弱引用。
- 當(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)引用斷開(kāi),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);
- 常見(jiàn)的方法是讓消費(fèi)者線程不斷地循環(huán)檢查變量是否符合預(yù)期在while循環(huán)中設(shè)置不滿(mǎn)足的條件,如果條件滿(mǎn)足則退出while循環(huán),從而完成消費(fèi)者的工作。
存在如下問(wèn)題:
- 1)難以確保及時(shí)性;
- 2)難以降低開(kāi)銷(xiāo)。如果降低睡眠的時(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)系就如同開(kāi)關(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)里判斷條件是否滿(mǎn)足,如果條件不滿(mǎn)足,那么調(diào)用對(duì)象的wait()方法,被通知后仍要檢查條件。
- 條件滿(mǎn)足則執(zhí)行對(duì)應(yīng)的邏輯。
synchronized(對(duì)象){
while(條件不滿(mǎn)足){
對(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 ;
- 客戶(hù)端獲取連接的過(guò)程被設(shè)定為等待超時(shí)的模式,也就是在1000毫秒內(nèi)如果無(wú)法獲取到可用連接,將會(huì)返回給客戶(hù)端一個(gè)null。
- 設(shè)定連接池的大小為10個(gè),然后通過(guò)調(diào)節(jié)客戶(hù)端的線程數(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í)和工作能夠有所幫助!