并發(fā)
線程的5種狀態(tài)
java.lang.Object的常用方法
- getClass() 獲取類結(jié)構(gòu)信息
- toString() 把對(duì)象轉(zhuǎn)變成字符串
- hashCode() 獲取哈希碼
- equals(Object) 默認(rèn)比較對(duì)象的地址值是否相等,子類可以重寫比較規(guī)則
- notify() 多線程中喚醒功能
- notifyAll() 多線程中喚醒所有等待線程的功能
- wait() 讓持有對(duì)象鎖的線程進(jìn)入等待
- wait(long timeout) 讓持有對(duì)象鎖的線程進(jìn)入等待,設(shè)置超時(shí)毫秒數(shù)時(shí)間
- wait(long timeout,int nanos) 讓持有對(duì)象鎖的線程進(jìn)入等待,設(shè)置超時(shí)納秒數(shù)時(shí)間
為什么Java把wait與notify放在Object中?
- 功能角度
- wait與notify的原始目的,是多線程場(chǎng)景下,某條件觸發(fā)另一邏輯,該條件對(duì)應(yīng)的直接關(guān)系為某種對(duì)象,進(jìn)而對(duì)應(yīng)為Object,其對(duì)應(yīng)為內(nèi)存資源。
- Thread對(duì)應(yīng)為CPU,與具體條件不是直接關(guān)系,Thread是對(duì)象的執(zhí)行依附者。
- 內(nèi)存角度
- 線程的同步需要Monitor的管理,其與實(shí)際操作系統(tǒng)的重型資源(鎖)相關(guān)。
- 只有涉及多線程的場(chǎng)景,才需要線程同步,如果wait與notify放在Thread,則每個(gè)Thread都需要分配Monitor,浪費(fèi)資源。
- 如果放在Object,單線程場(chǎng)景不分配Monitor,只在多線程分配。分配Monitor的方法為檢測(cè)threadId的不同。
sleep、yield、wait、join的區(qū)別
- sleep:Thread類的方法,必須帶一個(gè)時(shí)間參數(shù)。會(huì)讓當(dāng)前線程休眠進(jìn)入阻塞狀態(tài)并釋放CPU,提供其他線程運(yùn)行的機(jī)會(huì)且不考慮優(yōu)先級(jí),但如果有同步鎖,則sleep不會(huì)釋放鎖即其他線程無(wú)法獲得同步鎖,可通過調(diào)用interrupt()方法來喚醒休眠線程。針對(duì)一個(gè)線程
- wait:Object類的方法,只能在同步環(huán)境中被調(diào)用,必須放在循環(huán)體和同步代碼塊中,執(zhí)行該方法的線程會(huì)釋放鎖,進(jìn)入線程等待池中等待被再次喚醒(notify隨機(jī)喚醒,notifyAll全部喚醒,線程結(jié)束自動(dòng)喚醒),喚醒后放入鎖池中競(jìng)爭(zhēng)同步鎖
- yield:讓出CPU調(diào)度,Thread類的方法,類似sleep只是不能由用戶指定暫停多長(zhǎng)時(shí)間,并且yield()方法只能讓同優(yōu)先級(jí)的線程有執(zhí)行的機(jī)會(huì)。 yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。調(diào)用yield方法只是一個(gè)建議,告訴線程調(diào)度器我的工作已經(jīng)做的差不多了,可以讓別的相同優(yōu)先級(jí)的線程使用CPU了,沒有任何機(jī)制保證采納。
- join:一種特殊的wait,當(dāng)前運(yùn)行線程調(diào)用另一個(gè)線程的join方法,當(dāng)前線程進(jìn)入阻塞狀態(tài)直到另一個(gè)線程運(yùn)行結(jié)束等待該線程終止。注意該方法也需要捕捉異常。等待調(diào)用join方法的線程結(jié)束,再繼續(xù)執(zhí)行。如:t.join();//主要用于等待t線程運(yùn)行結(jié)束,若無(wú)此句,main則會(huì)執(zhí)行完畢,導(dǎo)致結(jié)果不可預(yù)測(cè)。
為何不推薦使用stop()和suspend()方法?
- stop()方法不安全。它會(huì)解除由線程獲取的所有鎖定,而且如果對(duì)象處于一種不連貫狀態(tài),那么其他線程能在那種狀態(tài)下檢查和修改它們。結(jié)果很難檢查出真正的問題所在。
- suspend()方法容易發(fā)生死鎖。調(diào)用suspend()的時(shí)候,目標(biāo)線程會(huì)停下來,但卻仍然持有在這之前獲得的鎖定。此時(shí),其他任何線程都不能訪問鎖定的資源,除非被"掛起"的線程恢復(fù)運(yùn)行。對(duì)任何線程來說,如果它們想恢復(fù)目標(biāo)線程,同時(shí)又試圖使用任何一個(gè)鎖定的資源,就會(huì)造成死鎖。所以不應(yīng)該使用suspend(),而應(yīng)在自己的Thread類中置入一個(gè)標(biāo)志,指出線程應(yīng)該活動(dòng)還是掛起。若標(biāo)志指出線程應(yīng)該掛起,便用 wait()命其進(jìn)入等待狀態(tài)。若標(biāo)志指出線程應(yīng)當(dāng)恢復(fù),則用一個(gè)notify()重新啟動(dòng)線程。
8種線程同步方法
線程安全:同一時(shí)刻只能有一個(gè)線程訪問共享變量。
- synchronized同步方法
- synchronized同步代碼塊
- Lock
- Volatile
- wait與notify
- Threadlocal
- 阻塞隊(duì)列
- 使用原子變量
Synchronized在靜態(tài)方法和非靜態(tài)方法的區(qū)別
synchronized 的用法可以從兩個(gè)維度上面分類:
- 根據(jù)修飾對(duì)象分類
- 修飾代碼塊
- synchronized(this|object) {}
- synchronized(類.class) {}
- 修飾方法
- 修飾非靜態(tài)方法(對(duì)象鎖)
- 修飾靜態(tài)方法(類鎖)
- 根據(jù)獲取的鎖分類
- 獲取對(duì)象鎖:線程同時(shí)開始,同時(shí)結(jié)束
- synchronized(this|object) {}
- 修飾非靜態(tài)方法
- 獲取類鎖:采用類鎖一次只能通過一個(gè)。
- synchronized(類.class) {}
- 修飾靜態(tài)方法,非靜態(tài)方法
- 使用
- 對(duì)于靜態(tài)方法,由于此時(shí)對(duì)象還未生成,所以只能采用類鎖。
- 只要采用類鎖,就會(huì)攔截所有線程,只能讓一個(gè)線程訪問。
- 對(duì)于對(duì)象鎖(this),如果是同一個(gè)實(shí)例,就會(huì)按順序訪問,但是如果是不同實(shí)例,就可以同時(shí)訪問。
- 如果對(duì)象鎖跟訪問的對(duì)象沒有關(guān)系,那么就會(huì)都同時(shí)訪問。
線程的創(chuàng)建方式
啟動(dòng)線程有如下三種方式:
- 繼承Thread類創(chuàng)建線程類
(1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務(wù)。因此把run()方法稱為執(zhí)行體。
(2)創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建了線程對(duì)象。
(3)調(diào)用線程對(duì)象的start()方法來啟動(dòng)該線程。
package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重寫run方法,run方法的方法體就是現(xiàn)場(chǎng)執(zhí)行體 public void run(){ for(;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i< 100;i++){ System.out.println(Thread.currentThread().getName()+" : "+i); if(i==20) { new FirstThreadTest().start(); } } } } |
上述代碼中Thread.currentThread()方法返回當(dāng)前正在執(zhí)行的線程對(duì)象。
- 通過Runnable接口創(chuàng)建線程類:使用實(shí)現(xiàn)Runnable接口的方式創(chuàng)建的線程可以處理同一資源,從而實(shí)現(xiàn)資源的共享.
- 適合多個(gè)相同程序代碼的線程去處理同一個(gè)資源(多線程內(nèi)的數(shù)據(jù)共享)
- 增加程序健壯性,數(shù)據(jù)被共享時(shí),仍然可以保持代碼和數(shù)據(jù)的分離和獨(dú)立
- 避免java特性中的單繼承限制
- 更能體現(xiàn)java面向?qū)ο蟮脑O(shè)計(jì)特點(diǎn)
(1)定義runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。
(2)創(chuàng)建 Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為Thread的target來創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象。
(3)調(diào)用線程對(duì)象的start()方法來啟動(dòng)該線程。
package com.thread; public class RunnableThreadTest implements Runnable{ private int i; public void run(){ for(i = 0;i <100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i < 100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i==20){ RunnableThreadTest rtt = new RunnableThreadTest(); new Thread(rtt,"新線程1").start(); new Thread(rtt,"新線程2").start(); } } } } |
- 通過Callable和Future創(chuàng)建線程
(1)創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值。
(2)創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,使用FutureTask類來包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該Callable對(duì)象的call()方法的返回值。
(3)使用FutureTask對(duì)象作為Thread對(duì)象的target創(chuàng)建并啟動(dòng)新線程。
(4)調(diào)用FutureTask對(duì)象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值
package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer>{ public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++){ System.out.println(Thread.currentThread().getName()+" 的變量i的值"+i); if(i==20) { new Thread(ft,"有返回值的線程").start(); } } try{ System.out.println("子線程的返回值:"+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
@Override public Integer call() throws Exception{ int i = 0; for(;i<100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } return i; } } |
Java中提供的線程池
Executors類提供了4種不同的線程池:newCachedThreadPool, newFixedThreadPool, newSingleThreadExecutor, newScheduledThreadPool
- newCachedThreadPool:用來創(chuàng)建一個(gè)可以無(wú)限擴(kuò)大的線程池,適用于負(fù)載較輕的場(chǎng)景,執(zhí)行短期異步任務(wù)。(可以使得任務(wù)快速得到執(zhí)行,因?yàn)槿蝿?wù)時(shí)間執(zhí)行短,可以很快結(jié)束,也不會(huì)造成cpu過度切換)
- newFixedThreadPool:創(chuàng)建一個(gè)固定大小的線程池,因?yàn)椴捎脽o(wú)界的阻塞隊(duì)列,所以實(shí)際線程數(shù)量永遠(yuǎn)不會(huì)變化,適用于負(fù)載較重的場(chǎng)景,對(duì)當(dāng)前線程數(shù)量進(jìn)行限制。(保證線程數(shù)可控,不會(huì)造成線程過多,導(dǎo)致系統(tǒng)負(fù)載更為嚴(yán)重)
- newSingleThreadExecutor:創(chuàng)建一個(gè)單線程的線程池,適用于需要保證順序執(zhí)行各個(gè)任務(wù)。容易OOM。
- newScheduledThreadPool:適用于執(zhí)行延時(shí)或者周期性任務(wù)。
自定義線程池(ThreadPoolExector)
new ThreadPoolExecutor( 2, 9, 1L, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy()); |
- 七大參數(shù):
- corePoolSize(常駐核心線程數(shù)):當(dāng)向線程池提交一個(gè)任務(wù)時(shí),若線程池已創(chuàng)建的線程數(shù)小于corePoolSize,即便此時(shí)存在空閑線程,也會(huì)通過創(chuàng)建一個(gè)新線程來執(zhí)行該任務(wù),直到已創(chuàng)建的線程數(shù)大于或等于corePoolSize時(shí),(除了利用提交新任務(wù)來創(chuàng)建和啟動(dòng)線程(按需構(gòu)造),也可以通過 prestartCoreThread() 或 prestartAllCoreThreads() 方法來提前啟動(dòng)線程池中的基本線程。)
- maximumPoolSize(線程池最大線程數(shù)):線程池所允許的最大線程個(gè)數(shù)。當(dāng)隊(duì)列滿了,且已創(chuàng)建的線程數(shù)小于maximumPoolSize,則線程池會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù)。另外,對(duì)于無(wú)界隊(duì)列,可忽略該參數(shù)。
- keepAliveTime(線程存活保持時(shí)間):當(dāng)線程池中線程數(shù)大于核心線程數(shù)時(shí),線程的空閑時(shí)間如果超過線程存活時(shí)間,那么這個(gè)線程就會(huì)被銷毀,直到線程池中的線程數(shù)小于等于核心線程數(shù)。
- unit(時(shí)間單位)
- workQueue(任務(wù)隊(duì)列):用于傳輸和保存等待執(zhí)行任務(wù)的阻塞隊(duì)列。
- threadFactory(線程工廠):用于創(chuàng)建新線程。threadFactory創(chuàng)建的線程也是采用new Thread()方式,threadFactory創(chuàng)建的線程名都具有統(tǒng)一的風(fēng)格:pool-m-thread-n(m為線程池的編號(hào),n為線程池內(nèi)的線程編號(hào))。
- handler(線程飽和策略):當(dāng)線程池和隊(duì)列都滿了,再加入線程會(huì)執(zhí)行此策略。
- 四種拒絕策略:
- ThreadPoolExecutor.AbortPolicy(默認(rèn)):丟棄任務(wù)并拋出RejectedExecutionException異常。
- ThreadPoolExecutor.DiscardPolicy:丟棄任務(wù),但是不拋出異常。
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新提交被拒絕的任務(wù)
- ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程(提交任務(wù)的線程)處理該任務(wù)
- 配置線程池
- CPU密集型任務(wù):盡量使用較小的線程池,一般為CPU核心數(shù)+1。 因?yàn)?/span>CPU密集型任務(wù)使得CPU使用率很高,若開過多的線程數(shù),會(huì)造成CPU過度切換。
- IO密集型任務(wù):可以使用稍大的線程池,一般為2*CPU核心數(shù)。IO密集型任務(wù)CPU使用率并不高,因此可以讓CPU在等待IO的時(shí)候有其他線程去處理別的任務(wù),充分利用CPU時(shí)間。CPU核心線程數(shù)/1-阻塞系數(shù)(0.8~0.9)
- 混合型任務(wù):可以將任務(wù)分成IO密集型和CPU密集型任務(wù),然后分別用不同的線程池去處理。
線程池的狀態(tài)
- RUNNING:這是最正常的狀態(tài),接受新的任務(wù),處理等待隊(duì)列中的任務(wù)。線程池的初始化狀態(tài)是RUNNING。線程池被一旦被創(chuàng)建,就處于RUNNING狀態(tài),并且線程池中的任務(wù)數(shù)為0。
- SHUTDOWN:不接受新的任務(wù)提交,但是會(huì)繼續(xù)處理等待隊(duì)列中的任務(wù)。調(diào)用線程池的shutdown()方法時(shí),線程池由RUNNING -> SHUTDOWN。
- STOP:不接受新的任務(wù)提交,不再處理等待隊(duì)列中的任務(wù),中斷正在執(zhí)行任務(wù)的線程。調(diào)用線程池的shutdownNow()方法時(shí),線程池由(RUNNING or SHUTDOWN ) -> STOP。
- TIDYING(整理狀態(tài)):所有的任務(wù)都銷毀了,workCount 為 0,線程池的狀態(tài)在轉(zhuǎn)換為 TIDYING 狀態(tài)時(shí),會(huì)執(zhí)行鉤子方法 terminated()。因?yàn)?/span>terminated()在ThreadPoolExecutor類中是空的,所以用戶想在線程池變?yōu)?/span>TIDYING時(shí)進(jìn)行相應(yīng)的處理;可以通過重載terminated()函數(shù)來實(shí)現(xiàn)。
- TERMINATED:線程池處在TIDYING狀態(tài)時(shí),執(zhí)行完terminated()之后,就會(huì)由 TIDYING -> TERMINATED。
線程池中shutdown()和shutdownNow()方法的區(qū)別
shutdown只是將線程池的狀態(tài)設(shè)置為SHUTWDOWN狀態(tài),正在執(zhí)行的任務(wù)會(huì)繼續(xù)執(zhí)行下去,沒有被執(zhí)行的則中斷。而shutdownNow則是將線程池的狀態(tài)設(shè)置為STOP,正在執(zhí)行的任務(wù)則被停止,沒被執(zhí)行任務(wù)的則返回。
線程池中 submit() 和 execute() 方法的區(qū)別
- execute():只能執(zhí)行 Runnable 類型的任務(wù)。
- submit():可以執(zhí)行 Runnable 和 Callable 類型的任務(wù)。
- submit()能獲取返回值(異步)以及處理Exception。
java線程池與tomcat線程池策略算法上的異同
- java線程池
- 如果當(dāng)前運(yùn)行的線程,少于corePoolSize,則創(chuàng)建一個(gè)新的線程來執(zhí)行任務(wù)。
- 如果運(yùn)行的線程等于或多于 corePoolSize,將任務(wù)加入 BlockingQueue。
- 如果 BlockingQueue 內(nèi)的任務(wù)超過上限,則創(chuàng)建新的線程來處理任務(wù)。
- 如果創(chuàng)建的線程超出 maximumPoolSize,任務(wù)將被拒絕策略拒絕。
- tomcat線程池
- 如果當(dāng)前運(yùn)行的線程,少于corePoolSize,則創(chuàng)建一個(gè)新的線程來執(zhí)行任務(wù)。
- 如果線程數(shù)大于 corePoolSize了,Tomcat 的線程不會(huì)直接把線程加入到無(wú)界的阻塞隊(duì)列中,而是去判斷submitedCount(已經(jīng)提交線程數(shù))是否等于 maximumPoolSize。
- 如果等于,表示線程池已經(jīng)滿負(fù)荷運(yùn)行,不能再創(chuàng)建線程了,直接把線程提交到隊(duì)列,
- 如果不等于,則需要判斷,是否有空閑線程可以消費(fèi)。
- 如果有空閑線程則加入到阻塞隊(duì)列中,等待空閑線程消費(fèi)。
- 如果沒有空閑線程,嘗試創(chuàng)建新的線程。(這一步保證了使用無(wú)界隊(duì)列,仍然可以利用線程的 maximumPoolSize)。
- 如果總線程數(shù)達(dá)到 maximumPoolSize,則繼續(xù)嘗試把線程加入 BlockingQueue 中。
- 如果 BlockingQueue 達(dá)到上限(假如設(shè)置了上限),被默認(rèn)線程池啟動(dòng)拒絕策略,tomcat 線程池會(huì) catch 住拒絕策略拋出的異常,再次把嘗試任務(wù)加入中 BlockingQueue 中。
- 再次加入失敗,啟動(dòng)拒絕策略。
- 源碼分析
Tomcat源碼中,為擴(kuò)展線程池,主要修改了:
- 自定義ThreadPoolExecutor,直接繼承JDK的ThreadPoolExecutor,重寫部分邏輯,線程池核心方法execute(),Tomcat簡(jiǎn)單做了修改,還是將工作任務(wù)交給父類,也就是Java原生線程池處理,但增加了一個(gè)重試策略。如果原生線程池執(zhí)行拒絕策略的情況,拋出 RejectedExecutionException 異常。這里將會(huì)捕獲,然后重新再次嘗試將任務(wù)加入到 TaskQueue ,盡最大可能執(zhí)行任務(wù)。
public void execute(Runnable command, long timeout, TimeUnit unit) { // 它是一個(gè) AtomicInteger 變量,將會(huì)實(shí)時(shí)統(tǒng)計(jì)已經(jīng)提交到線程池中,但還沒有執(zhí)行結(jié)束的任務(wù)。也就是說 submittedCount 等于線程池隊(duì)列中的任務(wù)數(shù)加上線程池工作線程正在執(zhí)行的任務(wù)。 this.submittedCount.incrementAndGet();
try { super.execute(command); } catch (RejectedExecutionException var9) { if (!(super.getQueue() instanceof TaskQueue)) { this.submittedCount.decrementAndGet(); throw var9; }
TaskQueue queue = (TaskQueue)super.getQueue();
try { //拒絕后,再嘗試入隊(duì),還不行則拋出異常 if (!queue.force(command, timeout, unit)) { this.submittedCount.decrementAndGet(); throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull")); } } catch (InterruptedException var8) { this.submittedCount.decrementAndGet(); throw new RejectedExecutionException(var8); } } } |
- 實(shí)現(xiàn)TaskQueue,直接繼承LinkedBlockingQueue ,重寫 offer 方法。
public class TaskQueue extends LinkedBlockingQueue<Runnable> { ...... //tomcat-util-10.0.0-M6.jar public boolean offer(Runnable o) { if (this.parent == null) { //1.若沒有給出tomcat線程池對(duì)象,則調(diào)用父類方法 return super.offer(o); } else if (this.parent.getPoolSize() == this.parent.getMaximumPoolSize()) { //2.若當(dāng)前線程數(shù)已達(dá)到最大線程數(shù),則放入阻塞隊(duì)列 return super.offer(o); } else if (this.parent.getSubmittedCount() <= this.parent.getPoolSize()) { //3.若當(dāng)前已提交任務(wù)數(shù)量小于等于最大線程數(shù),說明此時(shí)有空閑線程。此時(shí)將任務(wù)放入隊(duì)列中,立刻會(huì)有空閑線程來處理該任務(wù) return super.offer(o); } else { //4.若當(dāng)前線程數(shù)小于最大線程數(shù),返回false,此時(shí)線程池將會(huì)創(chuàng)建新線程?。?! return this.parent.getPoolSize() < this.parent.getMaximumPoolSize() ? false : super.offer(o); } } } |
ThreadLocal
https://segmentfault.com/a/1190000037728236?utm_source=tag-newest
ThreadLocal提供了線程內(nèi)存儲(chǔ)變量的能力,這些變量不同之處在于每一個(gè)線程讀取的變量是對(duì)應(yīng)的互相獨(dú)立的。通過get和set方法就可以得到當(dāng)前線程對(duì)應(yīng)的值。
- ThreadLocal和Synchronized都是為了解決多線程中相同變量的訪問沖突問題,不同的點(diǎn)是:
- Synchronized是通過線程等待,犧牲時(shí)間來解決訪問沖突
- ThreadLocal是通過每個(gè)線程單獨(dú)一份存儲(chǔ)空間,犧牲空間來解決沖突,并且相比于Synchronized,ThreadLocal具有線程隔離的效果,只有在線程內(nèi)才能獲取到對(duì)應(yīng)的值,線程外則不能訪問到想要的值。
- 應(yīng)用場(chǎng)景
- 數(shù)據(jù)庫(kù)連接池的實(shí)現(xiàn):獲取connection
- 提升性能和安全,如SimpleDateFormat
- 底層實(shí)現(xiàn)
- ThreadLocal僅僅是個(gè)變量訪問的入口;
- 每一個(gè)Thread對(duì)象都有一個(gè)ThreadLocalMap對(duì)象,這個(gè)ThreadLocalMap持有對(duì)象的引用;
- ThreadLocalMap以當(dāng)前的threadLocal對(duì)象為key,以真正的存儲(chǔ)對(duì)象為value。get()方法時(shí)通過threadLocal實(shí)例就可以找到綁定在當(dāng)前線程上的副本對(duì)象。
- 每個(gè)線程都有一個(gè)屬于自己的ThreadLocalMap類,用于關(guān)聯(lián)多個(gè)以ThreadLocal對(duì)象為key,以要存儲(chǔ)的數(shù)據(jù)為value的Entry對(duì)象。線程內(nèi)部無(wú)法進(jìn)行GC,其內(nèi)部存儲(chǔ)實(shí)體結(jié)構(gòu)Entry<ThreadLocal, T>繼承自java.lan.ref.WeakReference,且該Entry對(duì)象的key是一個(gè)弱引用對(duì)象,當(dāng)jvm發(fā)現(xiàn)內(nèi)存不足時(shí),會(huì)自動(dòng)回收弱引用指向的實(shí)例內(nèi)存,只回收ThreadLocal對(duì)象,而非整個(gè)Entry,所以線程變量中的T對(duì)象還是在內(nèi)存中存在的,所以內(nèi)存泄漏的問題還沒有完全解決。在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)清除線程ThreadLocalMap里所有key為null的value。
- 復(fù)用線程Threadlocal變量的解決方法:
- 保證每次都用新的值覆蓋線程變量;
- 保證在每個(gè)請(qǐng)求結(jié)束后清空線程變量。
- 使用ThreadLocal時(shí)遵守以下兩個(gè)原則:
①ThreadLocal申明為private static final。
Private與final 盡可能不讓他人修改變更引用,Static 表示為類屬性,只有在程序結(jié)束才會(huì)被回收。
②ThreadLocal使用后務(wù)必調(diào)用remove方法。
最簡(jiǎn)單有效的方法是使用后將其移除。
Synchornized底層實(shí)現(xiàn)
在理解鎖實(shí)現(xiàn)原理之前,先了解一下Java的對(duì)象頭和Monitor,在JVM中,對(duì)象是分成三部分存在的:對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)齊填充。
- 實(shí)例數(shù)據(jù)存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息,如果是數(shù)組的實(shí)例部分還包括數(shù)組的長(zhǎng)度,這部分內(nèi)存按4字節(jié)對(duì)齊;
- 對(duì)其填充不是必須部分,由于虛擬機(jī)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,對(duì)齊填充僅僅是為了使字節(jié)對(duì)齊。
- 對(duì)象頭是synchronized實(shí)現(xiàn)鎖的基礎(chǔ),因?yàn)?/span>synchronized申請(qǐng)鎖、上鎖、釋放鎖都與對(duì)象頭有關(guān)。對(duì)象頭主要結(jié)構(gòu)是由Mark Word 和 Class Metadata Address組成,其中Mark Word存儲(chǔ)對(duì)象的hashCode、鎖信息或分代年齡或GC標(biāo)志等信息,Class Metadata Address是類型指針指向?qū)ο蟮念惖脑獢?shù)據(jù),JVM通過該指針確定該對(duì)象是哪個(gè)類的實(shí)例。

鎖的類型和狀態(tài)(四種)在對(duì)象頭Mark Word中都有記錄,在申請(qǐng)鎖、鎖升級(jí)等過程中JVM都需要讀取對(duì)象的Mark Word數(shù)據(jù)。每一個(gè)鎖都對(duì)應(yīng)一個(gè)monitor對(duì)象,每個(gè)對(duì)象都存在著一個(gè)monitor與之關(guān)聯(lián),對(duì)象與其monitor之間的關(guān)系有存在多種實(shí)現(xiàn)方式,如monitor可以與對(duì)象一起創(chuàng)建銷毀或當(dāng)線程試圖獲取對(duì)象鎖時(shí)自動(dòng)生成,但當(dāng)一個(gè)monitor被某個(gè)線程持有后,它便處于鎖定狀態(tài)。
監(jiān)視器(Monitor)內(nèi)部如何線程同步?
監(jiān)視器和鎖在Java虛擬機(jī)中是一塊使用的。監(jiān)視器監(jiān)視一塊同步代碼塊,確保一次只有一個(gè)線程執(zhí)行同步代碼塊。每一個(gè)監(jiān)視器都和一個(gè)對(duì)象引用相關(guān)聯(lián)。線程在獲取鎖之前不允許執(zhí)行同步代碼。
synchronized可重入的實(shí)現(xiàn)
每個(gè)鎖關(guān)聯(lián)一個(gè)線程持有者和一個(gè)計(jì)數(shù)器。當(dāng)計(jì)數(shù)器為0時(shí)表示該鎖沒有被任何線程持有,那么任何線程都可能獲得該鎖而調(diào)用相應(yīng)方法。當(dāng)一個(gè)線程請(qǐng)求成功后,JVM會(huì)記下持有鎖的線程,并將計(jì)數(shù)器計(jì)為1。此時(shí)其他線程請(qǐng)求該鎖,則必須等待。而該持有鎖的線程如果再次請(qǐng)求這個(gè)鎖,就可以再次拿到這個(gè)鎖,同時(shí)計(jì)數(shù)器會(huì)遞增。當(dāng)線程退出一個(gè)synchronized方法/塊時(shí),計(jì)數(shù)器會(huì)遞減,如果計(jì)數(shù)器為0則釋放該鎖。
鎖的優(yōu)化
- 鎖升級(jí):鎖的4種狀態(tài):無(wú)鎖狀態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)(級(jí)別從低到高),鎖可以升級(jí)不可以降級(jí),但是偏向鎖狀態(tài)可以被重置為無(wú)鎖狀態(tài)。目的是為了提高獲得鎖和釋放鎖的效率。
- 鎖粗化:將多個(gè)連續(xù)的加鎖、解鎖操作連接在一起,擴(kuò)展成一個(gè)范圍更大的鎖,避免頻繁的加鎖解鎖操作。
- 鎖消除:Java虛擬機(jī)在JIT編譯時(shí)(可以簡(jiǎn)單理解為當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,又稱即時(shí)編譯),通過對(duì)運(yùn)行上下文的掃描,經(jīng)過逃逸分析,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖,通過這種方式消除沒有必要的鎖,可以節(jié)省毫無(wú)意義的請(qǐng)求鎖時(shí)間
Synchronized和Lock
- 實(shí)現(xiàn)層面不一樣。synchronized 是Java關(guān)鍵字,JVM層面實(shí)現(xiàn)加鎖和釋放鎖;Lock 是一個(gè)接口,在代碼層面實(shí)現(xiàn)加鎖和釋放鎖
- 是否自動(dòng)釋放鎖。synchronized 在線程代碼執(zhí)行完或出現(xiàn)異常時(shí)自動(dòng)釋放鎖;Lock 不會(huì)自動(dòng)釋放鎖,需要再 finally{}代碼塊顯式地中釋放鎖
- 是否一直等待。synchronized 會(huì)導(dǎo)致線程拿不到鎖一直等待;Lock 可以設(shè)置嘗試獲取鎖或者獲取鎖失敗一定時(shí)間超時(shí)
- 獲取鎖成功是否可知。synchronized 無(wú)法得知是否獲取鎖成功;Lock 可以通過 tryLock 獲得加鎖是否成功
- 功能復(fù)雜性。synchronized 加鎖可重入、不可中斷、非公平;Lock 可重入、可中斷、可公平和不公平、細(xì)分讀寫鎖提高效率
//創(chuàng)建一個(gè)公平鎖,構(gòu)造傳參true Lock lock = new ReentrantLock(true); |
- 鎖綁定多個(gè)條件,一個(gè)ReentrantLock對(duì)象可以同時(shí)綁定多個(gè)對(duì)象。ReenTrantLock提供了一個(gè)Condition類,用來實(shí)現(xiàn)分組喚醒需要喚醒的線程,而不是像synchronized要么隨機(jī)喚醒一個(gè)線程要么喚醒全部線程。
Volatile
- 使用volatile必須具備以下2個(gè)條件:
- 對(duì)變量的寫操作不依賴于當(dāng)前值
- 該變量沒有包含在具有其他變量的不變式中
- 實(shí)現(xiàn)方式
- 當(dāng)被volatile關(guān)鍵字修飾的資源有變化的時(shí)候,計(jì)算機(jī)會(huì)把CPU中的緩存資源重新刷新一遍,達(dá)到變量可見性一致的效果。
- 當(dāng)前計(jì)算機(jī)基本為多核多線程,在CPU中有一個(gè)緩存一致性的協(xié)議,由于這個(gè)協(xié)議使得CPU緩存資源刷新,最終達(dá)到變量可見性一致的效果。
- JVM對(duì)其禁止指令重排序在硬件層面的實(shí)現(xiàn)就是通過在volatile修飾的變量前后插入內(nèi)存屏障。
- lock:解鎖時(shí),jvm會(huì)強(qiáng)制刷新cpu緩存,導(dǎo)致當(dāng)前線程更改,對(duì)其他線程可見。
- volatile:標(biāo)記volatile的字段,在寫操作時(shí),會(huì)強(qiáng)制刷新cpu緩存,標(biāo)記volatile的字段,每次讀取都是直接讀內(nèi)存。
- final:即時(shí)編譯器在final寫操作后,會(huì)插入內(nèi)存屏障,來禁止重排序,保證可見性
- 使用場(chǎng)景
- 狀態(tài)標(biāo)記量
- Double check
Synchronized和Volatile
- synchronized 可以作用于變量、方法、對(duì)象,volatile 只能作用于變量。
- synchronized 可以保證線程間的有序性(無(wú)法保證線程內(nèi)的有序性,線程內(nèi)的代碼可能被 CPU 指令重排序)、原子性和可見性,volatile 只保證可見性和有序性,無(wú)法保證原子性。
- synchronized 線程阻塞,volatile 線程不阻塞。
- volatile 本質(zhì)是告訴jvm當(dāng)前變量在寄存器中的值是不安全的需要從內(nèi)存中讀??;sychronized 則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問到,請(qǐng)求該變量的其他線程被阻塞。
- volatile 標(biāo)記的變量不會(huì)被編譯器優(yōu)化;synchronized 標(biāo)記的變量可以被編譯器優(yōu)化。
copyonwrite
- 實(shí)現(xiàn)就是寫時(shí)復(fù)制,在往集合中添加數(shù)據(jù)的時(shí)候,先拷貝存儲(chǔ)的數(shù)組,然后添加元素到拷貝好的數(shù)組中,然后用現(xiàn)在的數(shù)組去替換成員變量的數(shù)組(就是get等讀取操作讀取的數(shù)組)。這個(gè)機(jī)制和讀寫鎖是一樣的,但是比讀寫鎖有改進(jìn)的地方,那就是讀取的時(shí)候可以寫入的 ,這樣省去了讀寫之間的競(jìng)爭(zhēng)。同時(shí)寫入的時(shí)候怎么辦呢,當(dāng)然果斷還是加鎖。
- 適用場(chǎng)景:copyonwrite的機(jī)制雖然是線程安全的,但是在add操作的時(shí)候不停地拷貝是一件很費(fèi)時(shí)的操作,所以使用到這個(gè)集合的時(shí)候盡量不要出現(xiàn)頻繁的添加操作,而且在迭代的時(shí)候數(shù)據(jù)更新也是不及時(shí)的,數(shù)據(jù)太多的時(shí)候,實(shí)時(shí)性可能就差距很大。在多讀取,少添加的時(shí)候,效果還是不錯(cuò)的。
Java的鎖
- 公平鎖/非公平鎖
公平鎖:線程申請(qǐng)鎖的順序來獲取鎖;非公平鎖:允許加塞,有可能會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象。
- 優(yōu)先級(jí)反轉(zhuǎn):是指一個(gè)低優(yōu)先級(jí)的任務(wù)持有一個(gè)被高優(yōu)先級(jí)任務(wù)所需要的共享資源。高優(yōu)先任務(wù)由于因資源缺乏而處于受阻狀態(tài),一直等到低優(yōu)先級(jí)任務(wù)釋放資源為止。而低優(yōu)先級(jí)獲得的CPU時(shí)間少,如果此時(shí)有優(yōu)先級(jí)處于兩者之間的任務(wù),并且不需要那個(gè)共享資源,則該中優(yōu)先級(jí)的任務(wù)反而超過這兩個(gè)任務(wù)而獲得CPU時(shí)間。如果高優(yōu)先級(jí)等待資源時(shí)不是阻塞等待,而是忙循環(huán),則可能永遠(yuǎn)無(wú)法獲得資源,因?yàn)榇藭r(shí)低優(yōu)先級(jí)進(jìn)程無(wú)法與高優(yōu)先級(jí)進(jìn)程爭(zhēng)奪CPU時(shí)間,從而無(wú)法執(zhí)行,進(jìn)而無(wú)法釋放資源,造成的后果就是高優(yōu)先級(jí)任務(wù)無(wú)法獲得資源而繼續(xù)推進(jìn)。
- 解決方案:
(1)設(shè)置優(yōu)先級(jí)上限,給臨界區(qū)一個(gè)高優(yōu)先級(jí),進(jìn)入臨界區(qū)的進(jìn)程都將獲得這個(gè)高優(yōu)先級(jí),如果其他試圖進(jìn)入臨界區(qū)的進(jìn)程的優(yōu)先級(jí)都低于這個(gè)高優(yōu)先級(jí),那么優(yōu)先級(jí)反轉(zhuǎn)就不會(huì)發(fā)生。
(2)優(yōu)先級(jí)繼承,當(dāng)一個(gè)高優(yōu)先級(jí)進(jìn)程等待一個(gè)低優(yōu)先級(jí)進(jìn)程持有的資源時(shí),低優(yōu)先級(jí)進(jìn)程將暫時(shí)獲得高優(yōu)先級(jí)進(jìn)程的優(yōu)先級(jí)別,在釋放共享資源后,低優(yōu)先級(jí)進(jìn)程回到原來的優(yōu)先級(jí)別。嵌入式系統(tǒng)VxWorks就是采用這種策略。
(3)使用中斷禁止,通過禁止中斷來保護(hù)臨界區(qū),采用此種策略的系統(tǒng)只有兩種優(yōu)先級(jí):可搶占優(yōu)先級(jí)和中斷禁止優(yōu)先級(jí)。前者為一般進(jìn)程運(yùn)行時(shí)的優(yōu)先級(jí),后者為運(yùn)行于臨界區(qū)的優(yōu)先級(jí)。
- 線程饑餓:是指如果事務(wù)T1封鎖了數(shù)據(jù)R,事務(wù)T2又請(qǐng)求封鎖R,于是T2等待。T3也請(qǐng)求封鎖R,當(dāng)T1釋放了R上的封鎖后,系統(tǒng)首先批準(zhǔn)了T3的請(qǐng)求,T2仍然等待。然后T4又請(qǐng)求封鎖R,當(dāng)T3釋放了R上的封鎖之后,系統(tǒng)又批準(zhǔn)了T4的請(qǐng)求......T2可能永遠(yuǎn)等待,這就是饑餓。
- 獨(dú)占鎖/共享鎖
獨(dú)占鎖:寫鎖,ReentrantLock;共享鎖:讀鎖,ReadWriteLock
- 互斥鎖/讀寫鎖
獨(dú)享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實(shí)現(xiàn)。
- 可重入鎖(遞歸鎖)
是指在同一個(gè)線程在外層方法獲取鎖的時(shí)候,在進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖。ReentrantLock、Synchronized
- 自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗CPU。如CAS
- 樂觀鎖/悲觀鎖
- 分段鎖
分段鎖的設(shè)計(jì)目的是細(xì)化鎖的粒度,當(dāng)操作不需要更新整個(gè)數(shù)組的時(shí)候,就僅僅針對(duì)數(shù)組中的一項(xiàng)進(jìn)行加鎖操作。
- 偏向鎖/輕量級(jí)鎖/重量級(jí)鎖
非公平鎖和公平鎖在reetrantlock里的實(shí)現(xiàn)
對(duì)于非公平鎖,只要CAS設(shè)置同步狀態(tài)成功,則表示當(dāng)前線程獲取了鎖,而公平鎖還需要判斷當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn),如果有,則表示有線程比當(dāng)前線程更早請(qǐng)求獲取鎖,因此需要等待前驅(qū)線程獲取并釋放鎖之后才能繼續(xù)獲取鎖。
死鎖
- 定義:兩個(gè)線程或兩個(gè)以上線程因爭(zhēng)奪資源而出現(xiàn)線程互相等待的現(xiàn)象。
- 原因:
- 循環(huán)等待條件:若干資源形成一種頭尾相接的循環(huán)等待資源關(guān)系。
- 互斥條件:一個(gè)資源一次只能被一個(gè)進(jìn)程訪問。
- 請(qǐng)求保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。進(jìn)程至少已經(jīng)占有一個(gè)資源,但又申請(qǐng)新的資源;由于該資源已被另外進(jìn)程占有,此時(shí)該進(jìn)程阻塞;但是,它在等待新資源之時(shí),仍繼續(xù)占用已占有的資源。
- 不剝奪條件:進(jìn)程已經(jīng)獲得的資源,在未使用完之前不能強(qiáng)行剝奪,而只能由該資源的占有者進(jìn)程自行釋放。
- 解決方法:
- 銀行家算法,操作系統(tǒng)按照銀行家制定的規(guī)則為進(jìn)程分配資源,當(dāng)進(jìn)程首次申請(qǐng)資源時(shí),要測(cè)試該進(jìn)程對(duì)資源的最大需求量,如果系統(tǒng)現(xiàn)存的資源可以滿足它的最大需求量則按當(dāng)前的申請(qǐng)量分配資源,否則就推遲分配。當(dāng)進(jìn)程在執(zhí)行中繼續(xù)申請(qǐng)資源時(shí),先測(cè)試該進(jìn)程本次申請(qǐng)的資源數(shù)是否超過了該資源所剩余的總量。若超過則拒絕分配資源,若能滿足則按當(dāng)前的申請(qǐng)量分配資源,否則也要推遲分配。
- 指定獲取鎖的順序,并強(qiáng)制線程按照指定的順序獲取鎖。因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖,就不會(huì)出現(xiàn)死鎖了。
- 問題:CPU達(dá)到100%
- 排查:(Linux)
- top -c查看CPU資源使用情況,定位進(jìn)程
- ps –mp 進(jìn)程號(hào) –o THREAD,tid,time定位線程
- printf "%x\n" 12785將線程ID轉(zhuǎn)為16進(jìn)制格式(英文小寫)
- jstack 進(jìn)程號(hào) | grep 線程ID –A60定位具體出錯(cuò)代碼行
ReentrantLock(可重入鎖)和Synchornized區(qū)別
- ReentrantLock主要利用CAS+AQS來實(shí)現(xiàn)。ReentrantLock的基本實(shí)現(xiàn)可以概括為:先通過CAS嘗試獲取鎖,如果此時(shí)已經(jīng)有線程占據(jù)了鎖,那就加入CLH同步隊(duì)列并且被掛起。當(dāng)鎖被釋放之后,排在CLH同步隊(duì)列隊(duì)首的線程會(huì)被喚醒,然后CAS再次嘗試獲取鎖。
- 相同點(diǎn):都可以做到同一線程,同一把鎖,可重入代碼塊。
- 不同點(diǎn)
- Synchornized為非公平鎖。ReentrantLock可以實(shí)現(xiàn)公平鎖和非公平鎖,默認(rèn)非公平鎖??芍厝腈i又稱遞歸鎖,線程可以進(jìn)入任何一個(gè)已經(jīng)擁有的鎖同步著的代碼塊,可以避免死鎖。
- synchronized 是 JVM 層面實(shí)現(xiàn)的;ReentrantLock 是 JDK 代碼層面實(shí)現(xiàn)
- Synchornized競(jìng)爭(zhēng)鎖時(shí)會(huì)一直等待;reentrantLock可以嘗試獲取鎖,并得到獲取結(jié)果或者超時(shí)
- synchronized 在加鎖代碼塊執(zhí)行完或者出現(xiàn)異常,自動(dòng)釋放鎖;ReentrantLock 不會(huì)自動(dòng)釋放鎖,需要在 finally{} 代碼塊顯示釋放
- synchronized 控制等待和喚醒需要結(jié)合加鎖對(duì)象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和喚醒需要結(jié)合 Condition 的 await() 和 signal()、signalAll() 方法
JUC框架圖
CAS(compare and swap)
- 定義:Compare and Swap,比較并交換。CAS有3個(gè)操作數(shù):內(nèi)存值V、預(yù)期值A、要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。該操作是一個(gè)原子操作,被廣泛的應(yīng)用在Java的底層實(shí)現(xiàn)中。在Java中,CAS主要是由sun.misc.Unsafe這個(gè)類通過JNI調(diào)用CPU底層指令實(shí)現(xiàn)。
- 缺點(diǎn):
- 循環(huán)時(shí)間長(zhǎng),循環(huán)消耗CPU
- 只能保證一個(gè)共享變量的原子操作
- ABA問題:(貍貓換太子)A->B->A,解決方法:時(shí)間戳原子引用(版本控制)AutomicStampedReference類
- Java8優(yōu)化:
由于采用這種CAS機(jī)制是沒有對(duì)方法進(jìn)行加鎖的,所以,所有的線程都可以進(jìn)入increment()這個(gè)方法,假如進(jìn)入這個(gè)方法的線程太多,就會(huì)出現(xiàn)一個(gè)問題:每次有線程要執(zhí)行第三個(gè)步驟的時(shí)候,i的值老是被修改了,所以線程又到回到第一步繼續(xù)重頭再來。而這就會(huì)導(dǎo)致一個(gè)問題:由于線程太密集了,太多人想要修改i的值了,進(jìn)而大部分人都會(huì)修改不成功,白白著在那里循環(huán)消耗資源。
為了解決這個(gè)問題,Java8引入了一個(gè)cell[]數(shù)組,它的工作機(jī)制是這樣的:假如有5個(gè)線程要對(duì)i進(jìn)行自增操作,由于5個(gè)線程不是很多,起沖突的幾率較小,那就讓他們按照以往正常的那樣,采用CAS自增。但是,如果有100個(gè)線程要對(duì)i進(jìn)行自增操作,沖突就會(huì)大大增加,系統(tǒng)就會(huì)把這些線程分配到不同的cell數(shù)組元素去,假如cell[10]有10個(gè)元素,且元素的初始化值為0,那么系統(tǒng)就會(huì)把100個(gè)線程分成10組,每一組對(duì)cell數(shù)組其中的一個(gè)元素做自增操作,這樣到最后,cell數(shù)組10個(gè)元素的值都為10,系統(tǒng)再把這10個(gè)元素的值進(jìn)行匯總,進(jìn)而得到100,就等價(jià)于100個(gè)線程對(duì)i進(jìn)行了100次自增操作。
Lock實(shí)現(xiàn)類
所有的實(shí)現(xiàn)類:AQS(AbstractQueuedSynchronizer)、ReentrantLock、ReadWriteLock、CountDownLatch、Semphore。
- Semaphore類是一個(gè)計(jì)數(shù)信號(hào)量,必須由獲取它的線程釋放,通常用于限制可以訪問某些資源(物理或邏輯的)線程數(shù)目。也是采用的AQS的state技術(shù),通過state來控制線程數(shù)量,state表示線程操作的數(shù)量。
- ReadWriteLock類
AQS(AbstractQueuedSynchronizer)
- AQS的本質(zhì)上是一個(gè)同步器/阻塞鎖的基礎(chǔ)框架,其作用主要是提供加鎖、釋放鎖,并在內(nèi)部維護(hù)一個(gè)FIFO等待隊(duì)列,用于存儲(chǔ)由于鎖競(jìng)爭(zhēng)而阻塞的線程。
- 定義:是一個(gè)用于構(gòu)建鎖和同步容器的框架。它能降低構(gòu)建鎖和同步器的工作量,還可以避免處理多個(gè)位置上發(fā)生的競(jìng)爭(zhēng)問題。在基于AQS構(gòu)建的同步器中,只可能在一個(gè)時(shí)刻發(fā)生阻塞,從而降低上下文切換的開銷,并提高吞吐量。AQS支持獨(dú)占鎖(exclusive)和共享鎖(share)兩種模式。無(wú)論是獨(dú)占鎖還是共享鎖,本質(zhì)上都是對(duì)AQS內(nèi)部的一個(gè)變量state的獲取。state是一個(gè)原子的int變量,用來表示鎖狀態(tài)、資源數(shù)等。
- 獨(dú)占鎖:只能被一個(gè)線程獲取到(Reentrantlock)
- 共享鎖:可以被多個(gè)線程同時(shí)獲取(CountDownLatch,ReadWriteLock)
- AQS內(nèi)部的數(shù)據(jù)結(jié)構(gòu)與原理
AQS內(nèi)部實(shí)現(xiàn)了兩個(gè)隊(duì)列,一個(gè)同步隊(duì)列,一個(gè)條件隊(duì)列。
同步隊(duì)列的作用是:當(dāng)線程獲取資源失敗之后,就進(jìn)入同步隊(duì)列的尾部保持自旋等待,不斷判斷自己是否是鏈表的頭節(jié)點(diǎn),如果是頭節(jié)點(diǎn),就不斷嘗試獲取資源,獲取成功后則退出同步隊(duì)列。
條件隊(duì)列是為L(zhǎng)ock實(shí)現(xiàn)的一個(gè)基礎(chǔ)同步器,并且一個(gè)線程可能會(huì)有多個(gè)條件隊(duì)列,只有在使用了Condition(控制哪些線程會(huì)被喚醒)才會(huì)存在條件隊(duì)列。是需要與Lock配合使用的,提供多個(gè)等待集合,更精確的控制(底層是park/unpark機(jī)制)
- AQS同步隊(duì)列中的節(jié)點(diǎn)狀態(tài)
- 自定義同步器實(shí)現(xiàn)時(shí)主要實(shí)現(xiàn)以下幾種方法:
- isHeldExclusively():該線程是否正在獨(dú)占資源,只有用到condition才需要去實(shí)現(xiàn)它。
- tryAcquire(int):獨(dú)占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
- tryRelease(int):獨(dú)占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
- tryAcquireShared(int):共享方式。嘗試獲取資源。負(fù)數(shù)表示失敗;0表示成功,但沒有剩余可用資源;正數(shù)表示成功,且有剩余資源。
- tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放后允許喚醒后續(xù)等待結(jié)點(diǎn)返回true,否則返回false。
以ReentrantLock為例,state初始化為0,表示未鎖定狀態(tài)。A線程lock()時(shí),會(huì)調(diào)用tryAcquire()獨(dú)占該鎖并將state+1。此后,其他線程再tryAcquire()時(shí)就會(huì)失敗,直到A線程unlock()到state=0(即釋放鎖)為止,其它線程才有機(jī)會(huì)獲取該鎖。當(dāng)然,釋放鎖之前,A線程自己是可以重復(fù)獲取此鎖的(state會(huì)累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多么次,這樣才能保證state是能回到零的。
再以CountDownLatch以例,任務(wù)分為N個(gè)子線程去執(zhí)行,state也初始化為N(注意N要與線程個(gè)數(shù)一致)。這N個(gè)子線程是并行執(zhí)行的,每個(gè)子線程執(zhí)行完后countDown()一次,state會(huì)CAS減1。等到所有子線程都執(zhí)行完后(即state=0),會(huì)unpark()主調(diào)用線程,然后主調(diào)用線程就會(huì)從await()函數(shù)返回,繼續(xù)后余動(dòng)作。
private transient volatile Node head; //等待隊(duì)列的頭 private transient volatile Node tail; //等待隊(duì)列的尾 private volatile int state; //原子性的鎖狀態(tài)位,ReentrantLock對(duì)該字段的調(diào)用是通過原子操作compareAndSetState進(jìn)行的 protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } |
cyclicbarrier和countdownlatch的區(qū)別
- CountDownLatch和CyclicBarrier都能夠?qū)崿F(xiàn)線程之間的等待
- CountDownLatch一般用于某個(gè)線程A或多個(gè)線程,等待若干個(gè)其他線程執(zhí)行完任務(wù)之后,它才執(zhí)行;CountDownLatch是通過一個(gè)計(jì)數(shù)器來實(shí)現(xiàn)的,計(jì)數(shù)器的初始值為線程的數(shù)量;調(diào)用await()方法的線程會(huì)被阻塞,直到計(jì)數(shù)器減到 0 的時(shí)候,才能繼續(xù)往下執(zhí)行;調(diào)用了await()進(jìn)行阻塞等待的線程,它們阻塞在Latch門閂/柵欄上;只有當(dāng)條件滿足的時(shí)候(countDown() N次,將計(jì)數(shù)減為0),它們才能同時(shí)通過這個(gè)柵欄;以此能夠?qū)崿F(xiàn),讓所有的線程站在一個(gè)起跑線上。
- CyclicBarrier一般用于一組線程互相等待至某個(gè)狀態(tài),然后這一組線程再同時(shí)執(zhí)行;
- CountDownLatch是減計(jì)數(shù),計(jì)數(shù)減為0后不能重用;而CyclicBarrier是加計(jì)數(shù),可置0后復(fù)用。
五種IO模型
網(wǎng)絡(luò)模型可以分為阻塞IO、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO和異步IO。網(wǎng)絡(luò)IO操作(read/write系統(tǒng)調(diào)用)其實(shí)分成了兩個(gè)步驟:第一:發(fā)起IO請(qǐng)求 ;第二:實(shí)際的IO讀寫(內(nèi)核態(tài)與用戶態(tài)的數(shù)據(jù)拷貝)。
阻塞與非阻塞IO的區(qū)別在于第一步,發(fā)起IO請(qǐng)求的進(jìn)程是否會(huì)被阻塞,如果阻塞直到IO操作完成才返回那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO;
同步IO和異步IO的區(qū)別在于第二步,實(shí)際的IO讀寫(內(nèi)核態(tài)與用戶態(tài)的數(shù)據(jù)拷貝)是否需要進(jìn)程參與,如果需要進(jìn)程參與則是同步IO,如果不需要進(jìn)程參與就是異步IO;如果實(shí)際的IO讀寫需要請(qǐng)求進(jìn)程參與,那么就是同步IO。因此阻塞IO、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO都是同步IO
- BIO:同步阻塞I/O模式,數(shù)據(jù)的讀取寫入必須阻塞在一個(gè)線程內(nèi)等待其完成。
- NIO:NIO是一種同步非阻塞的I/O模型,NIO中的所有IO都是從 Channel(通道)開始的。
- 從通道進(jìn)行數(shù)據(jù)讀取 :創(chuàng)建一個(gè)緩沖區(qū),然后請(qǐng)求通道讀取數(shù)據(jù)。
- 從通道進(jìn)行數(shù)據(jù)寫入 :創(chuàng)建一個(gè)緩沖區(qū),填充數(shù)據(jù),并要求通道寫入數(shù)據(jù)。
- 復(fù)用IO:基本思路就是通過slect或poll、epoll 來監(jiān)控多fd(文件描述符),來達(dá)到不必為每個(gè)fd創(chuàng)建一個(gè)對(duì)應(yīng)的監(jiān)控線程,從而減少線程資源創(chuàng)建的目的。
- 信號(hào)驅(qū)動(dòng)IO:信號(hào)驅(qū)動(dòng)IO不是用循環(huán)請(qǐng)求詢問的方式去監(jiān)控?cái)?shù)據(jù)就緒狀態(tài),而是在調(diào)用sigaction時(shí)候建立一個(gè)SIGIO的信號(hào)聯(lián)系,當(dāng)內(nèi)核數(shù)據(jù)準(zhǔn)備好之后再通過SIGIO信號(hào)通知線程數(shù)據(jù)準(zhǔn)備好后的可讀狀態(tài),當(dāng)線程收到可讀狀態(tài)的信號(hào)后,此時(shí)再向內(nèi)核發(fā)起recvfrom讀取數(shù)據(jù)的請(qǐng)求,因?yàn)樾盘?hào)驅(qū)動(dòng)IO的模型下應(yīng)用線程在發(fā)出信號(hào)監(jiān)控后即可返回,不會(huì)阻塞,所以這樣的方式下,一個(gè)應(yīng)用線程也可以同時(shí)監(jiān)控多個(gè)fd。
- AIO:異步非阻塞的IO模型。異步 IO 是基于事件和回調(diào)機(jī)制實(shí)現(xiàn)的,也就是應(yīng)用操作之后會(huì)直接返回,不會(huì)堵塞在那里,當(dāng)后臺(tái)處理完成,操作系統(tǒng)(內(nèi)核)會(huì)通知相應(yīng)的線程進(jìn)行后續(xù)的操作。
- 總結(jié):對(duì)于低負(fù)載、低并發(fā)的應(yīng)用程序,可以使用同步阻塞I/O來提升開發(fā)速率和更好的維護(hù)性;對(duì)于高負(fù)載、高并發(fā)的(網(wǎng)絡(luò))應(yīng)用,應(yīng)使用 NIO 的非阻塞模式來開發(fā)。
https://blog.csdn.net/weixin_43809223/article/details/100529810
系統(tǒng)如何提高并發(fā)性?
- 提高CPU并發(fā)計(jì)算能力
- 多進(jìn)程&多線程
- 減少進(jìn)程切換,使用線程,考慮進(jìn)程綁定CPU
- 減少使用不必要的鎖,考慮無(wú)鎖編程
- 考慮進(jìn)程優(yōu)先級(jí)
- 關(guān)注系統(tǒng)負(fù)載
- 改進(jìn)I/O模型
- DMA技術(shù)
- 異步I/O
- 改進(jìn)多路I/O就緒通知策略,epoll
- Sendfile
- 內(nèi)存映射
- 直接I/O
posted on 2021-08-24 20:56 嗨吖呀 閱讀(55) 評(píng)論(0) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)