Java 線程

Java 線程

一、線程創(chuàng)建

  • 繼承Thread類,重寫run方法
    1、定義Thread類的子類,并重寫該類的run方法
    2、創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建了線程對(duì)象
    3、調(diào)用線程對(duì)象的start()方法來(lái)啟動(dòng)該線程
  • 實(shí)現(xiàn)runnable 接口
    1、定義runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法
    2、創(chuàng)建 Runnable實(shí)現(xiàn)類的實(shí)例,并依此實(shí)例作為Thread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象。
    3、調(diào)用線程對(duì)象的start()方法來(lái)啟動(dòng)該線程
  • 實(shí)現(xiàn)callable接口
    1、創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值
    2、創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,使用FutureTask類來(lái)包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該Callable對(duì)象的call()方法的返回值。
    3、使用FutureTask對(duì)象作為Thread對(duì)象的target創(chuàng)建并啟動(dòng)新線程。
    4、調(diào)用FutureTask對(duì)象的get()方法來(lái)獲得子線程執(zhí)行結(jié)束后的返回值,調(diào)用get()方法會(huì)阻塞線程。
class callImpl implements Callable<String>{
    @Override
    public String call() throws Exception {
        return null;
    }
}
new Thread(new FutureTask<String>(new callImpl())).start();

  • 區(qū)別:繼承只有一次機(jī)會(huì),Runnable實(shí)現(xiàn)不占繼承機(jī)會(huì)且可以多實(shí)現(xiàn),Callable有返回值
  • 通過(guò)runnable接口和callable接口實(shí)現(xiàn):
    優(yōu)勢(shì)是還可以繼承其他類 并且多個(gè)線程公用一個(gè)target對(duì)象 非常適合多個(gè)相同線程來(lái)處理同一份資源 從而將cpu、代碼和數(shù)據(jù)分開
    缺點(diǎn)是 編程稍復(fù)雜 而且訪問(wèn)當(dāng)前線程需要使用Thread.currentThread
  • 通過(guò)繼承Thread類實(shí)現(xiàn):
    優(yōu)勢(shì)是編寫簡(jiǎn)單 無(wú)需使用Thread.currentThread 直接使用this即可獲取當(dāng)前線程
    缺點(diǎn)是無(wú)法繼承其他類

Callable、Future、FutureTask

  • Callable聲明了一個(gè)名稱為call()的方法,同時(shí)這個(gè)方法可以有返回值V,也可以拋出異常.
  • Future是一個(gè)接口,用來(lái)獲取異步計(jì)算結(jié)果,它可以中斷正在執(zhí)行的任務(wù),也可以查詢?nèi)蝿?wù)是否完成,還可以獲取任務(wù)完成后的結(jié)果。
  • FutureTask是future的實(shí)現(xiàn)類,它實(shí)現(xiàn)了runnable和future兩個(gè)接口。
    FutureTask是分狀態(tài)的
    (1)當(dāng)FutureTask處于未啟動(dòng)或已啟動(dòng)狀態(tài)時(shí),如果此時(shí)我們執(zhí)行FutureTask.get()方法將導(dǎo)致調(diào)用線程阻塞;當(dāng)FutureTask處于已完成狀態(tài)時(shí),執(zhí)行FutureTask.get()方法將導(dǎo)致調(diào)用線程立即返回結(jié)果或者拋出異常。
    (2)當(dāng)FutureTask處于未啟動(dòng)狀態(tài)時(shí),執(zhí)行FutureTask.cancel()方法將導(dǎo)致此任務(wù)永遠(yuǎn)不會(huì)執(zhí)行。
    當(dāng)FutureTask處于已啟動(dòng)狀態(tài)時(shí),執(zhí)行cancel(true)方法將以中斷執(zhí)行此任務(wù)線程的方式來(lái)試圖停止任務(wù),如果任務(wù)取消成功,cancel(...)返回true;但如果執(zhí)行cancel(false)方法將不會(huì)對(duì)正在執(zhí)行的任務(wù)線程產(chǎn)生影響(讓線程正常執(zhí)行到完成),此時(shí)cancel(...)返回false。
    當(dāng)任務(wù)已經(jīng)完成,執(zhí)行cancel(...)方法將返回false。

閱讀鏈接:

2.4線程并發(fā)工具-Callable、Future和FutureTask原理+源碼解析_wangle965235568的博客-CSDN博客_collable futuretask

二、生命周期

image-20200928152408815.png

主要狀態(tài)包括:創(chuàng)建、就緒、運(yùn)行、阻塞、終止。

  • 新建(NEW):新創(chuàng)建了一個(gè)線程對(duì)象。

  • 可運(yùn)行(RUNNABLE):線程對(duì)象創(chuàng)建后,其他線程(比如main線程)調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中,獲取cpu 的使用權(quán) 。

  • 運(yùn)行(RUNNING):可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu 時(shí)間片(timeslice) ,執(zhí)行程序代碼。

  • 阻塞(BLOCKED):阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄了cpu 使用權(quán),也即讓出了cpu timeslice,暫時(shí)停止運(yùn)行。直到線程進(jìn)入可運(yùn)行(runnable)狀態(tài),才有機(jī)會(huì)再次獲得cpu timeslice 轉(zhuǎn)到運(yùn)行(running)狀態(tài)。阻塞的情況分三種:

    (一). 等待阻塞:運(yùn)行(running)的線程執(zhí)行o.wait()方法,JVM會(huì)把該線程放入等待隊(duì)列(waitting queue)中。
    (二). 同步阻塞:運(yùn)行(running)的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池(lock pool)中。
    (三). 其他阻塞:運(yùn)行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法,或者發(fā)出了I/O請(qǐng)求時(shí),JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)。

  • 死亡(DEAD):線程run()、main() 方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。

三、線程方法

  • sleep()與wait():
    sleep:當(dāng)前線程睡眠;
    wait: 訪問(wèn)當(dāng)前對(duì)象的線程wait,前提是當(dāng)前方法必須加鎖,如果鎖不住方法,那么調(diào)用的對(duì)象wait無(wú)從談起。 wait的時(shí)候,鎖被釋放了,sleep的時(shí)候,鎖一直被持有。
    notify:叫醒當(dāng)前wait在這個(gè)對(duì)象上的線程。
  • join() 在A線程中調(diào)用B線程的join,意思是兩個(gè)線程合并,A線程要等待B線程執(zhí)行完才恢復(fù)執(zhí)行。
  • yield()讓出cpu,當(dāng)前線程進(jìn)入就緒隊(duì)列等待調(diào)度
  • getPriority()/setPriority():獲取與設(shè)置線程優(yōu)先級(jí)。 interrupt()中斷, 配合是否被中斷的方法一起使用,可以停止wait()方法和sleep()方法。
  • stop()非常粗暴,會(huì)強(qiáng)行把執(zhí)行到一半的線程終止,不推薦使用,已經(jīng)廢棄。

閱讀鏈接:

03. 就該這么學(xué)并發(fā) - 線程的阻塞 - 簡(jiǎn)書

四、死鎖

死鎖產(chǎn)生條件

1、互斥即資源是互斥的
2、請(qǐng)求和保持 即請(qǐng)求資源時(shí)遇到阻塞不會(huì)放棄已獲得的資源
3、不剝奪 即線程獲得資源后在未使用完之前不能被剝奪
4、循環(huán)等待

死鎖類型

靜態(tài)的鎖順序死鎖

如 線程1和2都要獲取A、B兩種資源 線程1已獲取A嘗試獲取B 線程2已獲取B嘗試獲取A
解決方案是以相同順序獲取資源

動(dòng)態(tài)的鎖順序死鎖

兩線程調(diào)用同一方法時(shí)傳入的參數(shù)順序是顛倒的造成死鎖
解決方案是使用System.identifyHashCode來(lái)定義鎖的順序

協(xié)作對(duì)象之間發(fā)生死鎖

解決方案是避免在持有鎖的情況下調(diào)用外部方法

閱讀鏈接:

死鎖

五、并發(fā)與線程同步

產(chǎn)生原因

多個(gè)線程對(duì)同一資源進(jìn)行操作導(dǎo)致數(shù)據(jù)不同步,它是解決線程安全的方式

  • 并行與并發(fā):1個(gè)核對(duì)1個(gè)線程是并行執(zhí)行,1個(gè)核對(duì)多個(gè)線程是并發(fā)執(zhí)行。
  • 線程安全:并發(fā)帶來(lái)競(jìng)爭(zhēng),競(jìng)爭(zhēng)的結(jié)果會(huì)讓多個(gè)線程同時(shí)寫某個(gè)共享變量時(shí)出現(xiàn)數(shù)據(jù)錯(cuò)誤問(wèn)題,該問(wèn)題即線程安全問(wèn)題。

并發(fā)三大原則

原子性

一個(gè)操作或多個(gè)操作要么全部執(zhí)行 要不都不執(zhí)行
在Java中只有讀數(shù)據(jù)和賦值(指將數(shù)字賦值給變量的操作)才是原子操作
可以用synchronize和lock來(lái)實(shí)現(xiàn)原子性 因?yàn)榧渔i后只有一個(gè)線程可以進(jìn)行操作

有序性

即程序的執(zhí)行順序按照代碼的先后順序執(zhí)行
指令重排序,一般來(lái)說(shuō),處理器為了提高程序運(yùn)行效率,可能會(huì)對(duì)輸入代碼進(jìn)行優(yōu)化,它不保證程序中各個(gè)語(yǔ)句的執(zhí)行先后順序同代碼中的順序一致,但是它會(huì)保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。
Java內(nèi)存模型具備一些先天的“有序性”,即不需要通過(guò)任何手段就能夠得到保證的有序性,這個(gè)通常也稱為 happens-before 原則。如果兩個(gè)操作的執(zhí)行次序無(wú)法從happens-before原則推導(dǎo)出來(lái),那么它們就不能保證它們的有序性,虛擬機(jī)可以隨意地對(duì)它們進(jìn)行重排序。

happen -before原則:
①程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作

②鎖定規(guī)則:一個(gè)unLock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作

③volatile變量規(guī)則:對(duì)一個(gè)變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作

④傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C

⑤線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每個(gè)一個(gè)動(dòng)作

⑥線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生

⑦線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行

⑧對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的finalize()方法的開始

可見(jiàn)性

當(dāng)多個(gè)線程可操作變量時(shí) 只要一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行修改其他線程可以立即看到
Java中的可見(jiàn)性可以用volatile關(guān)鍵字實(shí)現(xiàn) 這種變量當(dāng)值改變后會(huì)立即更新到主存 同時(shí)其他線程的備份會(huì)失效然后在使用時(shí)回去主存讀取最新值 此外也可以用synchronize和lock來(lái)實(shí)現(xiàn)可見(jiàn)性 加鎖后只有一個(gè)線程可以操作 操作完后會(huì)把新值寫會(huì)主存

線程同步方法

synchronized

實(shí)現(xiàn)同步的基礎(chǔ):Java中每個(gè)對(duì)象都可以作為鎖。當(dāng)線程試圖訪問(wèn)同步代碼時(shí),必須先獲得對(duì)象鎖,退出或拋出異常時(shí)必須釋放鎖
原子性:synchronize修飾的代碼塊、方法可以確保線程互斥訪問(wèn)因此可以保證原子性。
有序性:Java的happen-before原則,一個(gè)鎖的unlock happen-before該鎖的lock
可見(jiàn)性:對(duì)一個(gè)變量unlock之前要把值更新到主內(nèi)存中,同時(shí)對(duì)一個(gè)變量lock操作會(huì)清空工作內(nèi)存中的值,在使用變量的值時(shí)需要去主內(nèi)存中去取。

三種用法

  • 修飾實(shí)例方法 那么鎖住的是該實(shí)例對(duì)象
  • 修飾靜態(tài)方法 那么鎖住的是該類所有實(shí)例
  • 修飾代碼塊 那么鎖住的是括號(hào)內(nèi)的實(shí)例對(duì)象
//鎖住的是一個(gè)對(duì)象 該類的其他對(duì)象執(zhí)行同步方法時(shí)不會(huì)形成互斥
public synchronized void method1

//鎖住的是該類 該類所有對(duì)象執(zhí)行同步方法俊輝形成互斥
public synchronized static void method3

//鎖住括號(hào)內(nèi)的實(shí)例對(duì)象
synchronized(obj){
 //do something
}

原理

synchronize在軟件層面依賴JVM。
1、monitorenter:每個(gè)對(duì)象都是一個(gè)監(jiān)視器鎖(monitor)。當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán),過(guò)程如下:
1. 如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者;
2. 如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1;
3. 如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)

2、monitorexit:執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor的所有者。指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán)。

Synchronized的語(yǔ)義底層是通過(guò)一個(gè)monitor的對(duì)象來(lái)完成,其實(shí)wait/notify等方法也依賴于monitor對(duì)象,這就是為什么只有在同步的塊或者方法中才能調(diào)用wait/notify等方法,否則會(huì)拋出java.lang.IllegalMonitorStateException的異常的原因。

代碼塊同步 和 方法同步 ,兩者實(shí)現(xiàn)細(xì)節(jié)不同

  • 代碼塊同步
    編譯后會(huì)將指令插入到同步代碼塊開始的地方并在結(jié)束的地方退出 當(dāng)線程執(zhí)行進(jìn)入命令時(shí)會(huì)嘗試獲取對(duì)象對(duì)應(yīng)的monitor的所有權(quán)
  • 方法同步
    方法同步通過(guò)ACC_SYNCHRONIZED標(biāo)示符。當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor。在方法執(zhí)行期間,其他任何線程都無(wú)法再獲得同一個(gè)monitor對(duì)象。

兩者雖然實(shí)現(xiàn)細(xì)節(jié)不同,但本質(zhì)上都是對(duì)一個(gè)對(duì)象的監(jiān)視器(monitor)的獲取。任意一個(gè)對(duì)象都擁有自己的監(jiān)視器,當(dāng)同步代碼塊或同步方法時(shí),執(zhí)行方法的線程必須先獲得該對(duì)象的監(jiān)視器才能進(jìn)入同步塊或同步方法,沒(méi)有獲取到監(jiān)視器的線程將會(huì)被阻塞,并進(jìn)入同步隊(duì)列,狀態(tài)變?yōu)锽LOCKED。當(dāng)成功獲取監(jiān)視器的線程釋放了鎖后,會(huì)喚醒阻塞在同步隊(duì)列的線程,使其重新嘗試對(duì)監(jiān)視器的獲取。

推薦閱讀:

java-并發(fā)基石篇

深入理解Java并發(fā)之synchronized實(shí)現(xiàn)原理java synchronized

深入分析Synchronized原理(阿里面試題) - aspirant - 博客園

ReentrantLock

Lock接口

Lock,鎖對(duì)象。在Java中鎖是用來(lái)控制多個(gè)線程訪問(wèn)共享資源的方式JAVA SE5.0之后并發(fā)包中新增了Lock接口用來(lái)實(shí)現(xiàn)鎖的功能,它提供了與synchronized關(guān)鍵字類似的同步功能,只是在使用時(shí)需要顯式地獲取和釋放鎖,缺點(diǎn)就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性,可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性。
主要方法 :

  • void lock(): 執(zhí)行此方法時(shí),如果鎖處于空閑狀態(tài),當(dāng)前線程將獲取到鎖。相反,如果鎖已經(jīng)被其他線程持有,將禁用當(dāng)前線程,直到當(dāng)前線程獲取到鎖
  • boolean tryLock(): 如果鎖可用,則獲取鎖,并立即返回true,否則返回false. 該方法和lock()的區(qū)別在于,tryLock()只是”試圖”獲取鎖,如果鎖不可用,不會(huì)導(dǎo)致當(dāng)前線程被禁用,當(dāng)前線程仍然繼續(xù)往下執(zhí)行代碼。而lock()方法則是一定要獲取到鎖,如果鎖不可用,就一直等待,在未獲得鎖之前,當(dāng)前線程并不繼續(xù)向下執(zhí)行.
  • void unlock(): 執(zhí)行此方法時(shí),當(dāng)前線程將釋放持有的鎖. 鎖只能由持有者釋放,如果線程并不持有鎖,卻執(zhí)行該方法,可能導(dǎo)致異常的發(fā)生.
  • Condition newCondition(): 條件對(duì)象,獲取等待通知組件。該組件和當(dāng)前的鎖綁定,當(dāng)前線程只有獲取了鎖,才能調(diào)用該組件的await()方法,而調(diào)用后,當(dāng)前線程將釋放鎖

為什么要可重入

如果遞歸調(diào)用不可重入會(huì)導(dǎo)致死鎖。重入的實(shí)現(xiàn)是因?yàn)殒i有所有者和計(jì)數(shù)器兩個(gè)屬性

Lock實(shí)現(xiàn)原理

要想了解Lock的實(shí)現(xiàn)原理我們需要先了解一下AbstractQueuedSynchronizer簡(jiǎn)稱AQS,Java中的Lock接口的實(shí)現(xiàn)都是通過(guò)AQS實(shí)現(xiàn)的。

  • AbstractQueuedSynchronizer
    抽象隊(duì)列同步器,是用來(lái)構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架。AQS使用一個(gè)FIFO的隊(duì)列表示排隊(duì)等待鎖的線程,隊(duì)列頭部的線程執(zhí)行完畢之后,它會(huì)調(diào)用它的后繼的線程.AQS中有一個(gè)表示狀態(tài)的字段state,例如ReentrantLocky用它表示線程重入鎖的次數(shù),Semaphore用它表示剩余的許可數(shù)量。對(duì)state變量值的更新都采用CAS操作保證更新操作的原子性。
    AbstractQueuedSynchronizer繼承了AbstractOwnableSynchronizer,這個(gè)類只有一個(gè)變量:exclusiveOwnerThread,表示當(dāng)前占用該鎖的線程,并且提供了相應(yīng)的get,set方法。

AQS閱讀鏈接:

深度解析Java 8:JDK1.8 AbstractQueuedSynchronizer的實(shí)現(xiàn)分析(上)-InfoQ

深度解析Java 8:AbstractQueuedSynchronizer的實(shí)現(xiàn)分析(下)-InfoQ

總結(jié)

ReentrantLock提供了內(nèi)置鎖類似的功能和內(nèi)存語(yǔ)義。此外,ReetrantLock還提供了其它功能,包括定時(shí)的鎖等待、可中斷的鎖等待、公平性、以及實(shí)現(xiàn)非塊結(jié)構(gòu)的加鎖、Condition,對(duì)線程的等待和喚醒等操作更加靈活,一個(gè)ReentrantLock可以有多個(gè)Condition實(shí)例,所以更有擴(kuò)展性,不過(guò)ReetrantLock需要顯示的獲取鎖,并在finally中釋放鎖,否則后果很嚴(yán)重。ReentrantLock在性能上似乎優(yōu)于Synchronized,其中在jdk1.6中略有勝出,在1.5中是遠(yuǎn)遠(yuǎn)勝出。那么為什么不放棄內(nèi)置鎖,并在新代碼中都使用ReetrantLock?在java1.5中, 內(nèi)置鎖與ReentrantLock相比有例外一個(gè)優(yōu)點(diǎn):在線程轉(zhuǎn)儲(chǔ)中能給出在哪些調(diào)用幀中獲得了哪些鎖,并能夠檢測(cè)和識(shí)別發(fā)生死鎖的線程。Reentrant的非塊狀特性意味著,獲取鎖的操作不能與特定的棧幀關(guān)聯(lián)起來(lái),而內(nèi)置鎖卻可以。因?yàn)閮?nèi)置鎖時(shí)JVM的內(nèi)置屬性,所以未來(lái)更可能提升synchronized而不是ReentrantLock的性能。例如對(duì)線程封閉的鎖對(duì)象消除優(yōu)化,通過(guò)增加鎖粒度來(lái)消除內(nèi)置鎖的同步。

閱讀鏈接:

從ReentrantLock的實(shí)現(xiàn)看AQS的原理及應(yīng)用 - 美團(tuán)技術(shù)團(tuán)隊(duì)

解決多線程安全問(wèn)題-無(wú)非兩個(gè)方法synchronized和lock 具體原理以及如何 獲取鎖AQS算法 (百度-美團(tuán)) - aspirant - 博客園

synchronized和ReentrantLock的比較

1)Lock是一個(gè)接口,而synchronized是Java中的關(guān)鍵字,synchronized是內(nèi)置的語(yǔ)言實(shí)現(xiàn);
2)synchronized因?yàn)槭窃贘VM層面上實(shí)現(xiàn)的所以在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí),如果沒(méi)有主動(dòng)通過(guò)unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行,使用synchronized時(shí),等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷
4)通過(guò)Lock可以知道有沒(méi)有成功獲取鎖,而synchronized卻無(wú)法辦到。
5)Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。
6)一個(gè)ReentrantLock對(duì)象可以同時(shí)綁定多個(gè)Condition對(duì)象,而在synchronized中,鎖對(duì)象的wait()和notify()或notifyAll()方法可以實(shí)現(xiàn)一個(gè)隱含的條件,如果要和多余一個(gè)條件關(guān)聯(lián)的時(shí)候,就不得不額外地添加一個(gè)鎖,而ReentrantLock則無(wú)須這么做,只需要多次調(diào)用new Condition()方法即可
7)synchronized就是非公平鎖,它無(wú)法保證等待的線程獲取鎖的順序。ReentrantLock可以設(shè)置成公平鎖。
8)在性能上來(lái)說(shuō),如果競(jìng)爭(zhēng)資源不激烈,兩者的性能是差不多的,而 當(dāng)競(jìng)爭(zhēng)資源非常激烈時(shí)(即有大量線程同時(shí)競(jìng)爭(zhēng)),此時(shí)ReentrantLock的性能要遠(yuǎn)遠(yuǎn)優(yōu)于synchronized 。所以說(shuō),在具體使用時(shí)要根據(jù)適當(dāng)情況選擇。

Volatile關(guān)鍵字

特點(diǎn)

  • 可以保證可見(jiàn)性:保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。
    禁止進(jìn)行指令重排序
  • 保證有序性:當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行,且結(jié)果已經(jīng)對(duì)后面的操作可見(jiàn);在其后面的操作肯定還沒(méi)有進(jìn)行。在進(jìn)行指令優(yōu)化時(shí),不能將在對(duì)volatile變量的讀操作或者寫操作的語(yǔ)句放在其后面執(zhí)行,也不能把volatile變量后面的語(yǔ)句放到其前面執(zhí)行
  • 不保證原子性:要保證原子性可加鎖或者用AtomicInteger

原理

  • 如何保證可見(jiàn)性
    處理器為了提高處理速度,不直接和內(nèi)存進(jìn)行通訊,而是將系統(tǒng)內(nèi)存的數(shù)據(jù)獨(dú)到內(nèi)部緩存后再進(jìn)行操作,但操作完后不知什么時(shí)候會(huì)寫到內(nèi)存。如果對(duì)聲明了volatile變量進(jìn)行寫操作時(shí),JVM會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫會(huì)到系統(tǒng)內(nèi)存。 這一步確保了如果有其他線程對(duì)聲明了volatile變量進(jìn)行修改,則立即更新主內(nèi)存中數(shù)據(jù)。但這時(shí)候其他處理器的緩存還是舊的,所以在多處理器環(huán)境下,為了保證各個(gè)處理器緩存一致,每個(gè)處理會(huì)通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查 自己的緩存是否過(guò)期, 當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改了,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作時(shí),會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存把數(shù)據(jù)讀到處理器緩存里。 這一步確保了其他線程獲得的聲明了volatile變量都是從主內(nèi)存中獲取最新的。
  • 如何保證有序性
    Lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄),它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成。

應(yīng)用

狀態(tài)標(biāo)記量、單例模式的double check

推薦閱讀:

Java-并發(fā)基石篇

鎖類型

  • 重入鎖
    內(nèi)置鎖(synchronized)和Lock(ReentrantLock)都是可重入的,當(dāng)一個(gè)線程得到一個(gè)對(duì)象后,再次請(qǐng)求該對(duì)象鎖時(shí)是可以再次得到該對(duì)象的鎖的。具體概念就是:自己可以再次獲取自己的內(nèi)部鎖
  • 公平鎖
    CPU在調(diào)度線程的時(shí)候是在等待隊(duì)列里隨機(jī)挑選一個(gè)線程,由于這種隨機(jī)性所以是無(wú)法保證線程先到先得的(synchronized控制的鎖就是這種非公平鎖)。但這樣就會(huì)產(chǎn)生饑餓現(xiàn)象,即有些線程(優(yōu)先級(jí)較低的線程)可能永遠(yuǎn)也無(wú)法獲取CPU的執(zhí)行權(quán),優(yōu)先級(jí)高的線程會(huì)不斷的強(qiáng)制它的資源。那么如何解決饑餓問(wèn)題呢,這就需要公平鎖了。公平鎖可以保證線程按照時(shí)間的先后順序執(zhí)行,避免饑餓現(xiàn)象的產(chǎn)生。但公平鎖的效率比較低.ReentrantLock是公平鎖
  • 悲觀鎖
    悲觀鎖總是假設(shè)我們處于最壞的情況,即每次執(zhí)行臨界區(qū)代碼都會(huì)產(chǎn)生沖突,所以悲觀鎖在持有數(shù)據(jù)的時(shí)候總會(huì)把資源或者數(shù)據(jù)鎖住,這樣其他線程想要請(qǐng)求這個(gè)資源的時(shí)候就會(huì)阻塞,直到等到悲觀鎖把資源釋放為止。Java 中的 Synchronized 和 ReentrantLock 等是一種悲觀鎖思想的實(shí)現(xiàn)
  • 樂(lè)觀鎖
    樂(lè)觀鎖與悲觀鎖的思想相反,它總認(rèn)為資源和數(shù)據(jù)不會(huì)被別人所修改,所以讀取不會(huì)上鎖,但是樂(lè)觀鎖在進(jìn)行寫入操作的時(shí)候會(huì)判斷當(dāng)前數(shù)據(jù)是否被修改過(guò)。CAS是一種常見(jiàn)的樂(lè)觀鎖實(shí)現(xiàn)。

CAS

CAS 即 compare and swap(比較與交換),是一種有名的無(wú)鎖算法。即不使用鎖的情況下實(shí)現(xiàn)多線程之間的變量同步,也就是在沒(méi)有線程被阻塞的情況下實(shí)現(xiàn)變量的同步,所以也叫非阻塞同步。CAS 中涉及三個(gè)要素: 需要讀寫的內(nèi)存值 V、進(jìn)行比較的值 A、 擬寫入的新值 B
當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。
在java中可以通過(guò)鎖和循環(huán)CAS的方式來(lái)實(shí)現(xiàn)原子操作。Java中java.util.concurrent.atomic包相關(guān)類就是 CAS的實(shí)現(xiàn)

CAS的問(wèn)題

  • ABA問(wèn)題
    CAS需要在操作值的時(shí)候檢查下值有沒(méi)有發(fā)生變化,如果沒(méi)有發(fā)生變化則更新,但是如果一個(gè)值原來(lái)是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒(méi)有發(fā)生變化,但是實(shí)際上卻變化了。ABA問(wèn)題的解決思路就是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一,那么A-B-A 就會(huì)變成1A-2B-3A。從Java1.5開始JDK的atomic包里提供了一個(gè)類AtomicStampedReference來(lái)解決ABA問(wèn)題。這個(gè)類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。
  • 循環(huán)時(shí)間長(zhǎng)開銷大
  • 只能保證一個(gè)共享變量的原子操作
    循環(huán)CAS就無(wú)法保證操作的原子性,這個(gè)時(shí)候就可以用鎖,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來(lái)操作。比如有兩個(gè)共享變量i=2,j=a,合并一下ij=2a,然后用CAS來(lái)操作ij。從Java1.5開始JDK提供了AtomicReference類來(lái)保證引用對(duì)象之間的原子性,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作。

補(bǔ)充1: Java中Atomic類的使用分析

補(bǔ)充2:
Java內(nèi)存模型:
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中。每條線程中還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程所使用到的變量(這些變量是從主內(nèi)存中拷貝而來(lái))。線程對(duì)變量的所有操作(讀取,賦值)都必須在工作內(nèi)存中進(jìn)行。不同線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成。
存在問(wèn)題:
可能導(dǎo)致數(shù)據(jù)“臟讀”初始時(shí),兩個(gè)線程分別讀取i的值存入各自所在的工作內(nèi)存當(dāng)中,然后線程1進(jìn)行加1操作,然后把i的最新值11寫入到內(nèi)存。此時(shí)線程2的工作內(nèi)存當(dāng)中i的值還是10,進(jìn)行加1操作之后,i的值為11,然后線程2把i的值寫入內(nèi)存。

鎖優(yōu)化

無(wú)鎖>>>偏向鎖>>>輕量鎖>>>重量鎖,按該順序一次升級(jí),不可降級(jí)

六、多線程

線程池

優(yōu)勢(shì)

  • 降低系統(tǒng)資源消耗 通過(guò)重用已存在線程減少線程創(chuàng)建和銷毀造成的資源消耗
  • 提高響應(yīng)速度 當(dāng)有任務(wù)到達(dá)時(shí)無(wú)需創(chuàng)建新的線程便可立即執(zhí)行
  • 可控的線程并發(fā)數(shù) 若無(wú)限制創(chuàng)建線程 會(huì)額外消耗大量系統(tǒng)資源 從而可能導(dǎo)致阻塞系統(tǒng)或者 oom
  • 線程池提供了豐富功能 如定時(shí)、定期執(zhí)行任務(wù)等

Executor框架

ThreadPoolExecutor

參數(shù)

  • corePoolSize:
    線程池中核心線程數(shù) 默認(rèn)情況核心線程是一直存活在線程池中即使他們處于閑置狀態(tài) 。如果設(shè)置allowCoreThreadTimeOut屬性為true 那么當(dāng)閑置的核心線程等待超時(shí)后就會(huì)被銷毀 超時(shí)時(shí)間由KeepAliveTime制定
  • maximumPoolsize
    線程池允許的最大線程數(shù) 等于核心+非核心線程數(shù) 當(dāng)超過(guò)該數(shù)值后后續(xù)任務(wù)會(huì)被阻塞
  • keepAliveTime
    非核心線程的閑置超時(shí)時(shí)間
  • unit
    制定keepalivetime參數(shù)的時(shí)間單位可以是 天、小時(shí)、分鐘等
  • workQueue
    線程池中保存等待執(zhí)行的任務(wù)的阻塞隊(duì)列 通過(guò)execute提交的runnable對(duì)象都會(huì)存在該隊(duì)列中
  • threadFactory
    線程工廠 為線程池提供新線程的創(chuàng)建
  • handler
    是RejectedExecutionHandler對(duì)象,而RejectedExecutionHandler是一個(gè)接口,里面只有一個(gè)rejectedExecution方法。當(dāng)任務(wù)隊(duì)列已滿并且線程池中的活動(dòng)線程已經(jīng)達(dá)到所限定的最大值或者是無(wú)法成功執(zhí)行任務(wù),這時(shí)候ThreadPoolExecutor會(huì)調(diào)用RejectedExecutionHandler中的rejectedExecution方法。在ThreadPoolExecutor中有四個(gè)內(nèi)部類實(shí)現(xiàn)了RejectedExecutionHandler接口。在線程池中它默認(rèn)是AbortPolicy,在無(wú)法處理新任務(wù)時(shí)拋出RejectedExecutionException異常

使用

  • execute
    提交任務(wù) 沒(méi)有返回值
  • submit
    提交任務(wù) 會(huì)返回一個(gè)future 由此可判斷任務(wù)是否執(zhí)行成功 同時(shí)可以通過(guò)get方法獲取返回值如果子線程任務(wù)沒(méi)有完成,get方法會(huì)阻塞住直到任務(wù)完成,而使用get(long timeout, TimeUnit unit)方法則會(huì)阻塞一段時(shí)間后立即返回,這時(shí)候有可能任務(wù)并沒(méi)有執(zhí)行完

關(guān)閉

  • shutdown
    講線程池狀態(tài)設(shè)置為shutdown 然后中斷所有沒(méi)執(zhí)行的任務(wù)
  • shutdownNow
    設(shè)置狀態(tài)為stop 然后終端所有任務(wù)包括正在執(zhí)行的任務(wù)

線程池執(zhí)行流程

如果線程數(shù)量沒(méi)有達(dá)到核心線程的上限 就啟動(dòng)一個(gè)核心線程來(lái)執(zhí)行任務(wù)
如果當(dāng)前線程池線程數(shù)量超過(guò)核心線程數(shù) 任務(wù)會(huì)被插入到任務(wù)隊(duì)列中等待執(zhí)行
任務(wù)隊(duì)列滿后 如果線程池中線程數(shù)量未達(dá)到線程數(shù)量上限就啟動(dòng)非核心線程執(zhí)行任務(wù) 如果達(dá)到線程池線程數(shù)量上限就會(huì)拒絕執(zhí)行任務(wù) 并調(diào)用RejectedExecutionHandler中的rejectedExecution方法來(lái)通知調(diào)用者。

線程池種類

  • newFixedThreadPool
    特點(diǎn):最大線程數(shù)就是核心線程數(shù) 這樣可以更快響應(yīng)外界請(qǐng)求
  • newCachedThreadPool
    核心線程數(shù)為0 最大線程數(shù)為Integer.MAX_VALUE 當(dāng)線程池線程都處于活動(dòng)狀態(tài) 線程池會(huì)創(chuàng)建新的線程來(lái)處理新任務(wù) 線程的超時(shí)時(shí)間為60秒 就是說(shuō)如果線程池閑置60秒后是不存在任何線程的 此時(shí)他幾乎不占用任何系統(tǒng)資源
  • newScheduledThreadPool
    核心線程數(shù)固定 非核心線程幾乎沒(méi)有數(shù)量限制
    schedule(Runnable command, long delay, TimeUnit unit):延遲一定時(shí)間后執(zhí)行Runnable任務(wù);
    schedule(Callable callable, long delay, TimeUnit unit):延遲一定時(shí)間后執(zhí)行Callable任務(wù);
    scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):延遲一定時(shí)間后,以間隔period時(shí)間的頻率周期性地執(zhí)行任務(wù);
    scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit):與scheduleAtFixedRate()方法很類似,但是不同的是scheduleWithFixedDelay()方法的周期時(shí)間間隔是以上一個(gè)任務(wù)執(zhí)行結(jié)束到下一個(gè)任務(wù)開始執(zhí)行的間隔,而scheduleAtFixedRate()方法的周期時(shí)間間隔是以上一個(gè)任務(wù)開始執(zhí)行到下一個(gè)任務(wù)開始執(zhí)行的間隔,也就是這一些任務(wù)系列的觸發(fā)時(shí)間都是可預(yù)知的
  • newSingleThreadExecutor
    只有一個(gè)核心線程 對(duì)任務(wù)隊(duì)列大小無(wú)限制 也就是說(shuō)一個(gè)任務(wù)處于活躍狀態(tài)時(shí) 其他任務(wù)都會(huì)在任務(wù)隊(duì)列中排隊(duì)等候依次執(zhí)行

線程池選擇

  • cpu密集型任務(wù)
    線程數(shù)應(yīng)盡量少 如設(shè)置N+1
  • I/O密集型
    由于IO操作速度遠(yuǎn)低于CPU速度,那么在運(yùn)行這類任務(wù)時(shí),CPU絕大多數(shù)時(shí)間處于空閑狀態(tài),那么線程池可以配置盡量多些的線程,以提高CPU利用率,如2*N。
  • 混合型
    可以進(jìn)行拆分 然后根據(jù)各部分特點(diǎn)選擇合適的線程池

線程數(shù)量分配

如果 CPU 和 I/O 設(shè)備的利用率都很低,那么可以嘗試通過(guò)增加線程來(lái)提高吞吐量。
在單核時(shí)代,多線程主要就是用來(lái)平衡 CPU 和 I/O 設(shè)備的。如果程序只有 CPU 計(jì)算,而沒(méi)有 I/O 操作的話,多線程不但不會(huì)提升性能,還會(huì)使性能變得更差,原因是增加了線程切換的成本。但是在多核時(shí)代,純計(jì)算型的程序也可以利用多線程來(lái)提升性能。這是為什么呢?因?yàn)槔枚嗪丝梢越档晚憫?yīng)時(shí)間。
比如要計(jì)算 1~100億 的值,如果在四核的 CPU 上利用四個(gè)線程執(zhí)行,線程 A 計(jì)算 [1,25億),線程 B 計(jì)算 [25億,50億),線程 C 計(jì)算[50億,75億),線程 D 計(jì)算[75億,100億],之后在匯總,那么理論上應(yīng)該比一個(gè)線程計(jì)算快四倍。一個(gè)線程,對(duì)于四核的 CPU,CPU 利用率只有 25%,而四個(gè)線程,則能夠?qū)?CPU 的利用率提高到 100%。
對(duì)于 CPU 密集型計(jì)算,多線程本質(zhì)上是提升多核 CPU 的利用率,所以對(duì)于一個(gè)四核的 CPU,每個(gè)核一個(gè)線程,理論上創(chuàng)建四個(gè)線程就可以了,再多創(chuàng)建線程只是會(huì)增加線程切換的成本。所以,對(duì)于 CPU 密集型計(jì)算場(chǎng)景,理論上 “線程的數(shù)量 = CPU 核數(shù)” 就是最合適的。不過(guò)在工程上,線程的數(shù)量一般會(huì)設(shè)置為 “ CPU 核數(shù) +1 “。這樣的話,當(dāng)線程因?yàn)榕紶柕膬?nèi)存頁(yè)失效或其他原因?qū)е伦枞麜r(shí),這個(gè)額外的線程可以頂上,從而保證 CPU 的利用率。
對(duì)于 I/O 密集型的計(jì)算場(chǎng)景,最佳的線程數(shù)是與程序中 CPU 計(jì)算和 I/O 操作的耗時(shí)比相關(guān)的,可以總結(jié)為:
線程數(shù) = 1 + ( I/O 耗時(shí) / CPU 耗時(shí) )
不過(guò)上面這個(gè)公式只針對(duì)單核 CPU 的,至于多核 CPU,只需要等比擴(kuò)大即可:
線程數(shù) = CPU 核數(shù) * [ 1 + ( I/O 耗時(shí) / CPU 耗時(shí) )]

ThreadpoolExecutor源碼

深入理解Java線程池:ThreadPoolExecutor | Idea Buffer

Java線程池實(shí)現(xiàn)原理及其在美團(tuán)業(yè)務(wù)中的實(shí)踐 - 美團(tuán)技術(shù)團(tuán)隊(duì)

七、線程間通信

wait/notify

兩者是object類中定義的 且都是final的無(wú)法被其他類重寫

  • wait
    讓當(dāng)前線程進(jìn)入等待并釋放鎖 wait(long)則是等待一定時(shí)間 超時(shí)后自動(dòng)喚醒
    注意事項(xiàng):1、首先調(diào)用wait時(shí)必須確保當(dāng)前線程擁有monitor即擁有鎖 否則會(huì)出現(xiàn)異常 2、釋放鎖后等待其他線程來(lái)通知他 這樣他才能重新獲得鎖的擁有權(quán)和恢復(fù)執(zhí)行
    和sleep區(qū)別:wait釋放鎖 sleep不釋放鎖
  • notify
    通知當(dāng)前等待的線程 當(dāng)前線程執(zhí)行完畢后釋放鎖 并從等待線程中喚醒一個(gè) notifyAll則是喚醒所有等待線程
    注意事項(xiàng):1、喚醒的線程是隨機(jī)的2、被喚醒的線程是不能執(zhí)行的需要等待當(dāng)前線程執(zhí)行完并釋放鎖才可以

使用注意:1、不可過(guò)早notify 否則容易打亂程序運(yùn)行邏輯 使wait方法釋放鎖后可能永遠(yuǎn)無(wú)法被喚醒2、注意wait等待條件發(fā)生變化也容易造成程序邏輯混亂

Condition實(shí)現(xiàn)等待/通知

關(guān)鍵字synchronized與wait()和notify()/notifyAll()方法相結(jié)合可以實(shí)現(xiàn)等待/通知模式,類似ReentrantLock也可以實(shí)現(xiàn)同樣的功能,但需要借助于Condition對(duì)象
特殊之處:synchronized相當(dāng)于整個(gè)ReentrantLock對(duì)象只有一個(gè)單一的Condition對(duì)象情況。而一個(gè)ReentrantLock卻可以擁有多個(gè)Condition對(duì)象,來(lái)實(shí)現(xiàn)通知部分線程。
具體實(shí)現(xiàn)方式:假設(shè)有兩個(gè)Condition對(duì)象:ConditionA和ConditionB。那么由ConditionA.await()方法進(jìn)入等待狀態(tài)的線程,由ConditionA.signalAll()通知喚醒;由ConditionB.await()方法進(jìn)入等待狀態(tài)的線程,由ConditionB.signalAll()通知喚醒。

八、線程相關(guān)

ThreadLoacl

作用是提供線程相關(guān)聯(lián)的且與其他線程相互隔離、獨(dú)立的變量存取。
其提供的方法有,initValue、get\put\remove等。
內(nèi)部存儲(chǔ)變量是通過(guò)Thread持有的一個(gè)threadLoaclmap,key是當(dāng)前ThreadLoacl實(shí)例 value是要存入的值,所以即便是同一變量也可以按線程存儲(chǔ),且各個(gè)線程間變量值相互隔離。

ThreadLocalMap是用來(lái)存儲(chǔ)與線程關(guān)聯(lián)的value的哈希表,它具有HashMap的部分特性,比如容量、擴(kuò)容閾值等,它內(nèi)部通過(guò)Entry類來(lái)存儲(chǔ)key和value。Entry繼承自WeakReference,通過(guò)上述源碼super(k);可以知道,ThreadLocalMap是使用ThreadLocal的 作為Key的

最好的方式就是將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長(zhǎng),由于一直存在ThreadLocal的強(qiáng)引用,所以ThreadLocal也就不會(huì)被回收,也就能保證任何時(shí)候都能根據(jù)ThreadLocal的弱引用訪問(wèn)到Entry的value值,然后remove它,可以防止內(nèi)存泄露。

Java并發(fā)編程之ThreadLocal詳解_Stay Hungry, Stay Foolish-CSDN博客_java threadlocal

生產(chǎn)者消費(fèi)者模型

單一的生產(chǎn)者消費(fèi)者模型可采用wait/notify實(shí)現(xiàn),多生產(chǎn)者消費(fèi)者模型可采用BlockingQueue來(lái)實(shí)現(xiàn)。

    //多消費(fèi)者生產(chǎn)者模型

    public class PoolMax{
        private BlockingQueue<String> pool;

        public PoolMax(BlockingQueue<String> pool) {
            this.pool = pool;
        }

        public BlockingQueue<String> getPool() {
            return pool;
        }

        public void setPool(BlockingQueue<String> pool) {
            this.pool = pool;
        }
    }

    public class ProductMax extends Thread{
        PoolMax poolMax;

        public ProductMax(PoolMax poolMax) {
            this.poolMax = poolMax;
        }

        @Override
        public void run() {
            super.run();
            while (true){
                try {
                   String good= String.valueOf(System.currentTimeMillis());

                    poolMax.getPool().put(good);
                    Log.i(TAG, "run: product  "+good);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public class ConsumerMax extends Thread{
        PoolMax poolMax;

        public ConsumerMax(PoolMax poolMax) {
            this.poolMax = poolMax;
        }

        @Override
        public void run() {
            super.run();
            while (true){
                try {
                   String good= poolMax.getPool().take();
                    Log.i(TAG, "run: consumer "+good);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void testMax(){
        PoolMax poolMax=new PoolMax(new ArrayBlockingQueue<String>(20));
        for (int i = 0; i < 10; i++) {
          new ProductMax(poolMax).start();
          new ConsumerMax(poolMax).start();
        }

    }

其他閱讀鏈接:
https://github.com/Omooo/Android-Notes/blob/master/blogs/Java/%E5%B9%B6%E5%8F%91/Lock%20%E5%92%8C%20Condition.md

https://github.com/LRH1993/android_interview/blob/master/java/concurrence/synchronized-reentrantlock.md

https://github.com/LRH1993/android_interview/blob/master/java/concurrence.md

https://github.com/francistao/LearningNotes/blob/master/Part2/JavaConcurrent/Java%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md

Java并發(fā)專題 帶返回結(jié)果的批量任務(wù)執(zhí)行 CompletionService ExecutorService.invokeAll_Hongyang-CSDN博客_executorcompletionservice與executorservice與executor

https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651746764&idx=3&sn=37a532a64dea755a8d8bfb83437f0bc2&chksm=bd12a8818a65219753a3cd631da27cf9d8466096b48eec60768de89eed60ded1d2c44b3d6e0b&scene=38#wechat_redirect

阿里面試官的分享Java面試中需要準(zhǔn)備哪些多線程并發(fā)的技術(shù)要點(diǎn) - 簡(jiǎn)書

Java 并發(fā)專題 :閉鎖 CountDownLatch 之一家人一起吃個(gè)飯_Hongyang-CSDN博客_java 閉鎖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容