1、為什么要使用多線程
選擇多線程的原因,就是因?yàn)榭?。舉個例子:
如果要把1000塊磚搬到樓頂 ,假設(shè)到樓頂有幾個電梯,你覺得用一個電梯搬運(yùn)快,還是同時用幾個電梯同時搬運(yùn)快呢?這個電梯就可以理解為線程。
所以,我們使用多線程就是因?yàn)椋?在正確的場景下,設(shè)置恰當(dāng)數(shù)目的線程,可以用來程提高序的運(yùn)行速率。更專業(yè)點(diǎn)講,就是充分地利用CPU和I/O的利用率,提升程序運(yùn)行速率。
當(dāng)然,有利就有弊,多線程場景下,我們要保證線程安全,就需要考慮加鎖。加鎖如果不恰當(dāng),就很很耗性能。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
2. 創(chuàng)建線程有幾種方式?
Java中創(chuàng)建線程主要有以下這幾種方式:
定義Thread類的子類,并重寫該類的run方法
定義Runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法
定義Callable接口的實(shí)現(xiàn)類,并重寫該接口的call()方法,一般配合Future使用
線程池的方式
2.1 定義Thread類的子類,并重寫該類的run方法
?
?
public?class?ThreadTest?{ ????public?static?void?main(String[]?args)?{ ????????Thread?thread?=?new?MyThread(); ????????thread.start(); ????} } class?MyThread?extends?Thread?{ ????@Override ????public?void?run()?{ ????????System.out.println("關(guān)注公?眾號:芋道源碼"); ????} }
?
?
2.2 定義Runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法
?
?
public?class?ThreadTest?{ ????public?static?void?main(String[]?args)?{ ????????MyRunnable?myRunnable?=?new?MyRunnable(); ????????Thread?thread?=?new?Thread(myRunnable); ????????thread.start(); ????} } class?MyRunnable?implements?Runnable?{ ????@Override ????public?void?run()?{ ????????System.out.println("關(guān)注公眾號:芋道源碼"); ????} } //運(yùn)行結(jié)果: 關(guān)注公眾號:芋道源碼
?
?
2.3 定義Callable接口的實(shí)現(xiàn)類,并重寫該接口的call()方法
如果想要執(zhí)行的線程有返回,可以使用Callable。
?
?
public?class?ThreadTest?{ ????public?static?void?main(String[]?args)?throws?ExecutionException,?InterruptedException?{ ????????MyThreadCallable?mc?=?new?MyThreadCallable(); ????????FutureTask?ft?=?new?FutureTask<>(mc); ????????Thread?thread?=?new?Thread(ft); ????????thread.start(); ????????System.out.println(ft.get()); ????} } class?MyThreadCallable?implements?Callable?{ ????@Override ????public?String?call()throws?Exception?{ ????????return?"關(guān)注公眾號:芋道源碼"; ????} } //運(yùn)行結(jié)果: 關(guān)注公眾號:芋道源碼
?
?
2.4 線程池的方式
日常開發(fā)中,我們一般都是用線程池的方式執(zhí)行異步任務(wù)。
?
?
public?class?ThreadTest?{ ????public?static?void?main(String[]?args)?throws?Exception?{ ????????ThreadPoolExecutor?executorOne?=?new?ThreadPoolExecutor(5,?5,?1, ????????????????TimeUnit.MINUTES,?new?ArrayBlockingQueue(20),?new?CustomizableThreadFactory("Tianluo-Thread-pool")); ????????executorOne.execute(()?->?{ ????????????System.out.println("關(guān)注公眾號:芋道源碼"); ????????}); ????????//關(guān)閉線程池 ????????executorOne.shutdown(); ????} }
?
?
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
3. start()方法和run()方法的區(qū)別
其實(shí)start和run的主要區(qū)別如下:
start方法可以啟動一個新線程,run方法只是類的一個普通方法而已,如果直接調(diào)用run方法,程序中依然只有主線程這一個線程。
start方法實(shí)現(xiàn)了多線程,而run方法沒有實(shí)現(xiàn)多線程。
start不能被重復(fù)調(diào)用,而run方法可以。
start方法中的run代碼可以不執(zhí)行完,就繼續(xù)執(zhí)行下面的代碼,也就是說進(jìn)行了線程切換 。然而,如果直接調(diào)用run方法,就必須等待其代碼全部執(zhí)行完才能繼續(xù)執(zhí)行下面的代碼。
大家可以結(jié)合代碼例子來看看哈~
?
?
public?class?ThreadTest?{ ????public?static?void?main(String[]?args){ ????????Thread?t=new?Thread(){ ????????????public?void?run(){ ????????????????pong(); ????????????} ????????}; ????????t.start(); ????????t.run(); ????????t.run(); ????????System.out.println("好的,馬上去關(guān)注:關(guān)注公眾號:芋道源碼"+?Thread.currentThread().getName()); ????} ????static?void?pong(){ ????????System.out.println("關(guān)注公眾號:芋道源碼"+?Thread.currentThread().getName()); ????} } //輸出 關(guān)注公眾號:芋道源碼main 關(guān)注公眾號:芋道源碼main 好的,馬上去關(guān)注:關(guān)注公眾號:芋道源碼main 關(guān)注公眾號:芋道源碼Thread-0
?
?
4. 線程和進(jìn)程的區(qū)別
進(jìn)程是運(yùn)行中的應(yīng)用程序,線程是進(jìn)程的內(nèi)部的一個執(zhí)行序列
進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位。
一個進(jìn)程可以有多個線程。線程又叫做輕量級進(jìn)程,多個線程共享進(jìn)程的資源
進(jìn)程間切換代價大,線程間切換代價小
進(jìn)程擁有資源多,線程擁有資源少地址
進(jìn)程是存在地址空間的,而線程本身無地址空間,線程的地址空間是包含在進(jìn)程中的
舉個例子:
你打開QQ,開了一個進(jìn)程;打開了迅雷,也開了一個進(jìn)程。
在QQ的這個進(jìn)程里,傳輸文字開一個線程、傳輸語音開了一個線程、彈出對話框又開了一個線程。
所以運(yùn)行某個軟件,相當(dāng)于開了一個進(jìn)程。在這個軟件運(yùn)行的過程里(在這個進(jìn)程里),多個工作支撐的完成QQ的運(yùn)行,那么這“多個工作”分別有一個線程。
所以一個進(jìn)程管著多個線程。
通俗的講:“進(jìn)程是爹媽,管著眾多的線程兒子”...
5. 說一下 Runnable 和 Callable有什么區(qū)別?
Runnable接口中的run()方法沒有返回值,是void類型,它做的事情只是純粹地去執(zhí)行run()方法中的代碼而已;
Callable接口中的call()方法是有返回值的,是一個泛型。它一般配合Future、FutureTask一起使用,用來獲取異步執(zhí)行的結(jié)果。
Callable接口call()方法允許拋出異常;而Runnable接口run()方法不能繼續(xù)上拋異常;
大家可以看下它倆的API:
?
?
?@FunctionalInterface public?interface?Callable?{ ????/** ?????*?支持泛型V,有返回值,允許拋出異常 ?????*/ ????V?call()?throws?Exception; } @FunctionalInterface public?interface?Runnable?{ ????/** ?????*??沒有返回值,不能繼續(xù)上拋異常 ?????*/ ????public?abstract?void?run(); }
?
?
為了方便大家理解,寫了一個demo,小伙伴們可以看看哈:
?
?
/* ?*??@Author?關(guān)注公眾號:芋道源碼 ?*??@date?2022-07-11 ?*/ public?class?CallableRunnableTest?{ ????public?static?void?main(String[]?args)?{ ????????ExecutorService?executorService?=?Executors.newFixedThreadPool(5); ????????Callable?callable?=new?Callable ()?{ ????????????@Override ????????????public?String?call()?throws?Exception?{ ????????????????return?"你好,callable,關(guān)注公眾號:芋道源碼"; ????????????} ????????}; ????????//支持泛型 ????????Future ?futureCallable?=?executorService.submit(callable); ????????try?{ ????????????System.out.println("獲取callable的返回結(jié)果:"+futureCallable.get()); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????}?catch?(ExecutionException?e)?{ ????????????e.printStackTrace(); ????????} ????????Runnable?runnable?=?new?Runnable()?{ ????????????@Override ????????????public?void?run()?{ ????????????????System.out.println("你好呀,runnable,關(guān)注公眾號:芋道源碼"); ????????????} ????????}; ????????Future>?futureRunnable?=?executorService.submit(runnable); ????????try?{ ????????????System.out.println("獲取runnable的返回結(jié)果:"+futureRunnable.get()); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????}?catch?(ExecutionException?e)?{ ????????????e.printStackTrace(); ????????} ????????executorService.shutdown(); ????} } //運(yùn)行結(jié)果 獲取callable的返回結(jié)果:你好,callable,關(guān)注公眾號:芋道源碼 你好呀,runnable,關(guān)注公眾號:芋道源碼 獲取runnable的返回結(jié)果:null
?
?
6. 聊聊volatile作用,原理
volatile關(guān)鍵字是Java虛擬機(jī)提供的的最輕量級的同步機(jī)制。它作為一個修飾符,用來修飾變量。它保證變量對所有線程可見性,禁止指令重排,但是不保證原子性 。
我們先來一起回憶下java內(nèi)存模型(jmm):
Java虛擬機(jī)規(guī)范試圖定義一種Java內(nèi)存模型,來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺上都能達(dá)到一致的內(nèi)存訪問效果。
Java內(nèi)存模型規(guī)定所有的變量都是存在主內(nèi)存當(dāng)中,每個線程都有自己的工作內(nèi)存。這里的變量包括實(shí)例變量和靜態(tài)變量,但是不包括局部變量,因?yàn)榫植孔兞渴蔷€程私有的。
線程的工作內(nèi)存保存了被該線程使用的變量的主內(nèi)存副本,線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接操作主內(nèi)存。并且每個線程不能訪問其他線程的工作內(nèi)存。
volatile變量,保證新值能立即同步回主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新,所以我們說volatile保證了多線程操作變量的可見性。
volatile保證可見性和禁止指令重排,都跟內(nèi)存屏障有關(guān)。我們來看一段volatile使用的demo代碼:
?
?
/** ?*?關(guān)注公眾號:芋道源碼 ?**/ public?class?Singleton?{?? ???private?volatile?static?Singleton?instance;?? ???private?Singleton?(){}?? ???public?static?Singleton?getInstance()?{?? ???if?(instance?==?null)?{?? ???????synchronized?(Singleton.class)?{?? ???????if?(instance?==?null)?{?? ???????????instance?=?new?Singleton();?? ???????}?? ???????}?? ???}?? ???return?instance;?? ???}?? }??
?
?
編譯后,對比有volatile關(guān)鍵字和沒有volatile關(guān)鍵字時所生成的匯編代碼,發(fā)現(xiàn)有volatile關(guān)鍵字修飾時,會多出一個lock addl $0x0,(%esp),即多出一個lock前綴指令,lock指令相當(dāng)于一個內(nèi)存屏障
lock指令相當(dāng)于一個內(nèi)存屏障,它保證以下這幾點(diǎn):
重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置
將本處理器的緩存寫入內(nèi)存
如果是寫入動作,會導(dǎo)致其他處理器中對應(yīng)的緩存無效。
第2點(diǎn)和第3點(diǎn)就是保證volatile保證可見性的體現(xiàn)嘛,第1點(diǎn)就是禁止指令重排的體現(xiàn) 。
內(nèi)存屏障四大分類:(Load 代表讀取指令,Store代表寫入指令)
在每個volatile寫操作的前面插入一個StoreStore屏障。
在每個volatile寫操作的后面插入一個StoreLoad屏障。
在每個volatile讀操作的后面插入一個LoadLoad屏障。
在每個volatile讀操作的后面插入一個LoadStore屏障。
有些小伙伴,可能對這個還是有點(diǎn)疑惑,內(nèi)存屏障這玩意太抽象了。我們照著代碼看下吧:
內(nèi)存屏障保證前面的指令先執(zhí)行,所以這就保證了禁止了指令重排啦,同時內(nèi)存屏障保證緩存寫入內(nèi)存和其他處理器緩存失效,這也就保證了可見性,哈哈~有關(guān)于volatile的底層實(shí)現(xiàn),我們就討論到這哈~
7. 說說并發(fā)與并行的區(qū)別?
并發(fā)和并行最開始都是操作系統(tǒng) 中的概念,表示的是CPU執(zhí)行多個任務(wù)的方式。
順序:上一個開始執(zhí)行的任務(wù)完成后,當(dāng)前任務(wù)才能開始執(zhí)行
并發(fā):無論上一個開始執(zhí)行的任務(wù)是否完成,當(dāng)前任務(wù)都可以開始執(zhí)行
(即 A B 順序執(zhí)行的話,A 一定會比 B 先完成,而并發(fā)執(zhí)行則不一定。)
串行:有一個任務(wù)執(zhí)行單元,從物理上就只能一個任務(wù)、一個任務(wù)地執(zhí)行
并行:有多個任務(wù)執(zhí)行單元,從物理上就可以多個任務(wù)一起執(zhí)行
(即在任意時間點(diǎn)上,串行執(zhí)行時必然只有一個任務(wù)在執(zhí)行,而并行則不一定。)
知乎有個很有意思的回答 ,大家可以看下:
你吃飯吃到一半,電話來了,你一直到吃完了以后才去接,這就說明你不支持并發(fā)也不支持并行。
你吃飯吃到一半,電話來了,你停了下來接了電話,接完后繼續(xù)吃飯,這說明你支持并發(fā)。
你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支持并行。
并發(fā)的關(guān)鍵是你有處理多個任務(wù)的能力,不一定要同時。并行的關(guān)鍵是你有同時處理多個任務(wù)的能力。所以我認(rèn)為它們最關(guān)鍵的點(diǎn)就是:是否是同時 。
來源:知乎
8.synchronized 的實(shí)現(xiàn)原理以及鎖優(yōu)化?
synchronized是Java中的關(guān)鍵字,是一種同步鎖。synchronized關(guān)鍵字可以作用于方法或者代碼塊。
一般面試時。可以這么回答:
8.1 monitorenter、monitorexit、ACC_SYNCHRONIZED
如果synchronized 作用于代碼塊 ,反編譯可以看到兩個指令:monitorenter、monitorexit,JVM使用monitorenter和monitorexit兩個指令實(shí)現(xiàn)同步;如果作用synchronized作用于方法 ,反編譯可以看到ACCSYNCHRONIZED標(biāo)記,JVM通過在方法訪問標(biāo)識符(flags)中加入ACCSYNCHRONIZED來實(shí)現(xiàn)同步功能。
同步代碼塊是通過monitorenter和monitorexit來實(shí)現(xiàn),當(dāng)線程執(zhí)行到monitorenter的時候要先獲得monitor鎖,才能執(zhí)行后面的方法。當(dāng)線程執(zhí)行到monitorexit的時候則要釋放鎖。
同步方法是通過中設(shè)置ACCSYNCHRONIZED標(biāo)志來實(shí)現(xiàn),當(dāng)線程執(zhí)行有ACCSYNCHRONI標(biāo)志的方法,需要獲得monitor鎖。每個對象都與一個monitor相關(guān)聯(lián),線程可以占有或者釋放monitor。
8.2 monitor監(jiān)視器
monitor是什么呢?操作系統(tǒng)的管程(monitors)是概念原理,ObjectMonitor是它的原理實(shí)現(xiàn)。
在Java虛擬機(jī)(HotSpot)中,Monitor(管程)是由ObjectMonitor實(shí)現(xiàn)的,其主要數(shù)據(jù)結(jié)構(gòu)如下:
?
?
?ObjectMonitor()?{ ????_header???????=?NULL; ????_count????????=?0;?//?記錄個數(shù) ????_waiters??????=?0, ????_recursions???=?0; ????_object???????=?NULL; ????_owner????????=?NULL; ????_WaitSet??????=?NULL;??//?處于wait狀態(tài)的線程,會被加入到_WaitSet ????_WaitSetLock??=?0?; ????_Responsible??=?NULL?; ????_succ?????????=?NULL?; ????_cxq??????????=?NULL?; ????FreeNext??????=?NULL?; ????_EntryList????=?NULL?;??//?處于等待鎖block狀態(tài)的線程,會被加入到該列表 ????_SpinFreq?????=?0?; ????_SpinClock????=?0?; ????OwnerIsThread?=?0?; ??}
?
?
ObjectMonitor中幾個關(guān)鍵字段的含義如圖所示:
8.3 Java Monitor 的工作機(jī)理
想要獲取monitor的線程,首先會進(jìn)入_EntryList隊(duì)列。
當(dāng)某個線程獲取到對象的monitor后,進(jìn)入Owner區(qū)域,設(shè)置為當(dāng)前線程,同時計(jì)數(shù)器count加1。
如果線程調(diào)用了wait()方法,則會進(jìn)入WaitSet隊(duì)列。它會釋放monitor鎖,即將owner賦值為null,count自減1,進(jìn)入WaitSet隊(duì)列阻塞等待。
如果其他線程調(diào)用 notify() / notifyAll() ,會喚醒WaitSet中的某個線程,該線程再次嘗試獲取monitor鎖,成功即進(jìn)入Owner區(qū)域。
同步方法執(zhí)行完畢了,線程退出臨界區(qū),會將monitor的owner設(shè)為null,并釋放監(jiān)視鎖。
8.4 對象與monitor關(guān)聯(lián)
在HotSpot虛擬機(jī)中,對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header),實(shí)例數(shù)據(jù)(Instance Data)和對象填充(Padding) 。
對象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)、Class Pointer(類型指針) 。
Mark Word 是用于存儲對象自身的運(yùn)行時數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程 ID、偏向時間戳等。
重量級鎖,指向互斥量的指針。其實(shí)synchronized是重量級鎖,也就是說Synchronized的對象鎖,Mark Word鎖標(biāo)識位為10,其中指針指向的是Monitor對象的起始地址。
9. 線程有哪些狀態(tài)?
線程有6個狀態(tài),分別是:New, Runnable, Blocked, Waiting, Timed_Waiting, Terminated。
轉(zhuǎn)換關(guān)系圖如下:
New:線程對象創(chuàng)建之后、但還沒有調(diào)用start()方法,就是這個狀態(tài)。
?
?
/** ?*?關(guān)注公眾號:芋道源碼 ?*/ public?class?ThreadTest?{ ????public?static?void?main(String[]?args)?{ ????????Thread?thread?=?new?Thread(); ????????System.out.println(thread.getState()); ????} } //運(yùn)行結(jié)果: NEW
?
?
Runnable:它包括就緒(ready)和運(yùn)行中(running)兩種狀態(tài)。如果調(diào)用start方法,線程就會進(jìn)入Runnable狀態(tài)。它表示我這個線程可以被執(zhí)行啦(此時相當(dāng)于ready狀態(tài)),如果這個線程被調(diào)度器分配了CPU時間,那么就可以被執(zhí)行(此時處于running狀態(tài))。
?
?
public?class?ThreadTest?{ ????public?static?void?main(String[]?args)?{ ????????Thread?thread?=?new?Thread(); ????????thread.start(); ????????System.out.println(thread.getState()); ????} } //運(yùn)行結(jié)果: RUNNABLE
?
?
Blocked:阻塞的(被同步鎖或者IO鎖阻塞)。表示線程阻塞于鎖,線程阻塞在進(jìn)入synchronized關(guān)鍵字修飾的方法或代碼塊(等待獲取鎖 )時的狀態(tài)。比如前面有一個臨界區(qū)的代碼需要執(zhí)行,那么線程就需要等待,它就會進(jìn)入這個狀態(tài)。它一般是從RUNNABLE狀態(tài)轉(zhuǎn)化過來的。如果線程獲取到鎖,它將變成RUNNABLE狀態(tài)
?
?
Thread?t?=?new?Thread(new?Runnable?{ ????void?run()?{ ????????synchronized?(lock)?{?//?阻塞于這里,變?yōu)锽locked狀態(tài) ????????????//?dothings ????????}? ????} }); t.getState();?//新建之前,還沒開始調(diào)用start方法,處于New狀態(tài) t.start();?//調(diào)用start方法,就會進(jìn)入Runnable狀態(tài)
?
?
WAITING : 永久等待狀態(tài),進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動作(比如通知)。處于該狀態(tài)的線程不會被分配CPU執(zhí)行時間,它們要等待被顯式地喚醒,否則會處于無限期等待的狀態(tài)。一般Object.wait。
?
?
Thread?t?=?new?Thread(new?Runnable?{ ????void?run()?{ ????????synchronized?(lock)?{?//?Blocked ????????????//?dothings ????????????while?(!condition)?{ ????????????????lock.wait();?//?into?Waiting ????????????} ????????}? ????} }); t.getState();?//?New t.start();?//?Runnable
?
?
TIMED_WATING: 等待指定的時間重新被喚醒的狀態(tài)。有一個計(jì)時器在里面計(jì)算的,最常見就是使用Thread.sleep方法觸發(fā),觸發(fā)后,線程就進(jìn)入了Timed_waiting狀態(tài),隨后會由計(jì)時器觸發(fā),再進(jìn)入Runnable狀態(tài)。
?
?
Thread?t?=?new?Thread(new?Runnable?{ ????void?run()?{ ????????Thread.sleep(1000);?//?Timed_waiting ????} }); t.getState();?//?New t.start();?//?Runnable
?
?
終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完成。
再來看個代碼demo吧:
?
?
/** ?*?關(guān)注公眾號:芋道源碼 ?*/ public?class?ThreadTest?{ ????private?static?Object?object?=?new?Object(); ????public?static?void?main(String[]?args)?throws?Exception?{ ????????Thread?thread?=?new?Thread(new?Runnable()?{ ????????????@Override ????????????public?void?run()?{ ????????????????try?{ ????????????????????for(int?i?=?0;?i1000;?i++){ ????????????????????????System.out.print(""); ????????????????????} ????????????????????Thread.sleep(500); ????????????????????synchronized?(object){ ????????????????????????object.wait(); ????????????????????} ????????????????}?catch?(InterruptedException?e)?{ ????????????????????e.printStackTrace(); ????????????????} ????????????} ????????}); ????????Thread?thread1?=?new?Thread(new?Runnable()?{ ????????????@Override ????????????public?void?run()?{ ????????????????try?{ ????????????????????synchronized?(object){ ????????????????????????Thread.sleep(1000); ????????????????????} ????????????????????Thread.sleep(1000); ????????????????????synchronized?(object){ ????????????????????????object.notify(); ????????????????????} ????????????????}?catch?(InterruptedException?e)?{ ????????????????????e.printStackTrace(); ????????????????} ????????????} ????????}); ???????? ????????System.out.println("1"+thread.getState()); ????????thread.start(); ????????thread1.start(); ????????System.out.println("2"+thread.getState()); ????????while?(thread.isAlive()){ ????????????System.out.println("---"+thread.getState()); ????????????Thread.sleep(100); ????????} ????????System.out.println("3"+thread.getState()); ????} } 運(yùn)行結(jié)果: 1NEW 2RUNNABLE ---RUNNABLE ---TIMED_WAITING ---TIMED_WAITING ---TIMED_WAITING ---TIMED_WAITING ---BLOCKED ---BLOCKED ---BLOCKED ---BLOCKED ---BLOCKED ---WAITING ---WAITING ---WAITING ---WAITING ---WAITING ---WAITING ---WAITING ---WAITING ---WAITING
?
?
10. synchronized 和 ReentrantLock 的區(qū)別?
Synchronized是依賴于JVM實(shí)現(xiàn)的,而ReenTrantLock是API實(shí)現(xiàn)的。
在Synchronized優(yōu)化以前,synchronized的性能是比ReenTrantLock差很多的,但是自從Synchronized引入了偏向鎖,輕量級鎖(自旋鎖)后,兩者性能就差不多了。
Synchronized的使用比較方便簡潔,它由編譯器去保證鎖的加鎖和釋放。而ReenTrantLock需要手工聲明來加鎖和釋放鎖,最好在finally中聲明釋放鎖。
ReentrantLock可以指定是公平鎖還是?公平鎖。?synchronized只能是?公平鎖。
ReentrantLock可響應(yīng)中斷、可輪回,而Synchronized是不可以響應(yīng)中斷的
11. wait(),notify()和 suspend(), resume()之間的區(qū)別
wait()方法使得線程進(jìn)入阻塞等待狀態(tài),并且釋放鎖
notify()喚醒一個處于等待狀態(tài)的線程,它一般跟wait()方法配套使用。
suspend()使得線程進(jìn)入阻塞狀態(tài),并且不會自動恢復(fù),必須對應(yīng)的resume()被調(diào)用,才能使得線程重新進(jìn)入可執(zhí)行狀態(tài)。suspend()方法很容易引起死鎖問題。
resume()方法跟suspend()方法配套使用。
suspend()不建議使用 ,因?yàn)閟uspend()方法在調(diào)用后,線程不會釋放已經(jīng)占有的資 源(比如鎖),而是占有著資源進(jìn)入睡眠狀態(tài),這樣容易引發(fā)死鎖問題。
12. CAS?CAS 有什么缺陷,如何解決?
CAS,全稱是Compare and Swap,翻譯過來就是比較并交換;
CAS涉及3個操作數(shù),內(nèi)存地址值V,預(yù)期原值A(chǔ),新值B;如果內(nèi)存位置的值V與預(yù)期原A值相匹配,就更新為新值B,否則不更新
CAS有什么缺陷?
ABA 問題
并發(fā)環(huán)境下,假設(shè)初始條件是A,去修改數(shù)據(jù)時,發(fā)現(xiàn)是A就會執(zhí)行修改。但是看到的雖然是A,中間可能發(fā)生了A變B,B又變回A的情況。此時A已經(jīng)非彼A,數(shù)據(jù)即使成功修改,也可能有問題。
可以通過AtomicStampedReference 解決ABA問題 ,它,一個帶有標(biāo)記的原子引用類,通過控制變量值的版本來保證CAS的正確性。
循環(huán)時間長開銷
自旋CAS,如果一直循環(huán)執(zhí)行,一直不成功,會給CPU帶來非常大的執(zhí)行開銷。很多時候,CAS思想體現(xiàn),是有個自旋次數(shù)的,就是為了避開這個耗時問題~
只能保證一個變量的原子操作。
CAS 保證的是對一個變量執(zhí)行操作的原子性,如果對多個變量操作時,CAS 目前無法直接保證操作的原子性的??梢酝ㄟ^這兩個方式解決這個問題:1. 使用互斥鎖來保證原子性;2.將多個變量封裝成對象,通過AtomicReference來保證原子性。
13. 說說CountDownLatch與 CyclicBarrier 區(qū)別
CountDownLatch和CyclicBarrier都用于讓線程等待,達(dá)到一定條件時再運(yùn)行。主要區(qū)別是:
CountDownLatch:一個或者多個線程,等待其他多個線程完成某件事情之后才能執(zhí)行;
CyclicBarrier:多個線程互相等待,直到到達(dá)同一個同步點(diǎn),再繼續(xù)一起執(zhí)行。
舉個例子吧:
CountDownLatch:假設(shè)老師跟同學(xué)約定周末在公園門口集合,等人齊了再發(fā)門票。那么,發(fā)門票(這個主線程),需要等各位同學(xué)都到齊(多個其他線程都完成),才能執(zhí)行。
CyclicBarrier:多名短跑運(yùn)動員要開始田徑比賽,只有等所有運(yùn)動員準(zhǔn)備好,裁判才會鳴槍開始,這時候所有的運(yùn)動員才會疾步如飛。
14. 什么是多線程環(huán)境下的偽共享
14.1 什么是偽共享?
CPU的緩存是以緩存行(cache line)為單位進(jìn)行緩存的,當(dāng)多個線程修改相互獨(dú)立的變量,而這些變量又處于同一個緩存行時就會影響彼此的性能。這就是偽共享
現(xiàn)代計(jì)算機(jī)計(jì)算模型:
CPU執(zhí)行速度比內(nèi)存速度快好幾個數(shù)量級,為了提高執(zhí)行效率,現(xiàn)代計(jì)算機(jī)模型演變出CPU、緩存(L1,L2,L3),內(nèi)存的模型。
CPU執(zhí)行運(yùn)算時,如先從L1緩存查詢數(shù)據(jù),找不到再去L2緩存找,依次類推,直到在內(nèi)存獲取到數(shù)據(jù)。
為了避免頻繁從內(nèi)存獲取數(shù)據(jù),聰明的科學(xué)家設(shè)計(jì)出緩存行,緩存行大小為64字節(jié)。
也正是因?yàn)?strong>緩存行的存在 ,就導(dǎo)致了偽共享問題,如圖所示:
假設(shè)數(shù)據(jù)a、b被加載到同一個緩存行。
當(dāng)線程1修改了a的值,這時候CPU1就會通知其他CPU核,當(dāng)前緩存行(Cache line)已經(jīng)失效。
這時候,如果線程2發(fā)起修改b,因?yàn)榫彺嫘幸呀?jīng)失效了,所以「core2 這時會重新從主內(nèi)存中讀取該 Cache line 數(shù)據(jù)」。讀完后,因?yàn)樗薷腷的值,那么CPU2就通知其他CPU核,當(dāng)前緩存行(Cache line)又已經(jīng)失效。
醬紫,如果同一個Cache line的內(nèi)容被多個線程讀寫,就很容易產(chǎn)生相互競爭,頻繁回寫主內(nèi)存,會大大降低性能。
14.2 如何解決偽共享問題
既然偽共享是因?yàn)橄嗷オ?dú)立的變量存儲到相同的Cache line導(dǎo)致的,一個緩存行大小是64字節(jié)。那么,我們就可以使用空間換時間 的方法,即數(shù)據(jù)填充的方式 ,把獨(dú)立的變量分散到不同的Cache line~
來看個例子:
?
?
/** ?*?更多干貨內(nèi)容,關(guān)注公眾號:芋道源碼 ?*/ public?class?FalseShareTest??{ ????public?static?void?main(String[]?args)?throws?InterruptedException?{ ????????Rectangle?rectangle?=?new?Rectangle(); ????????long?beginTime?=?System.currentTimeMillis(); ????????Thread?thread1?=?new?Thread(()?->?{ ????????????for?(int?i?=?0;?i?100000000;?i++)?{ ????????????????rectangle.a?=?rectangle.a?+?1; ????????????} ????????}); ????????Thread?thread2?=?new?Thread(()?->?{ ????????????for?(int?i?=?0;?i?100000000;?i++)?{ ????????????????rectangle.b?=?rectangle.b?+?1; ????????????} ????????}); ????????thread1.start(); ????????thread2.start(); ????????thread1.join(); ????????thread2.join(); ????????System.out.println("執(zhí)行時間"?+?(System.currentTimeMillis()?-?beginTime)); ????} } class?Rectangle?{ ????volatile?long?a; ????volatile?long?b; } //運(yùn)行結(jié)果: 執(zhí)行時間2815
?
?
一個long類型是8字節(jié),我們在變量a和b之間不上7個long類型變量呢,輸出結(jié)果是啥呢?如下:
?
?
class?Rectangle?{ ????volatile?long?a; ????long?a1,a2,a3,a4,a5,a6,a7; ????volatile?long?b; } //運(yùn)行結(jié)果 執(zhí)行時間1113
?
?
可以發(fā)現(xiàn)利用填充數(shù)據(jù)的方式,讓讀寫的變量分割到不同緩存行,可以很好挺高性能~
15. Fork/Join框架的理解
Fork/Join框架是Java7提供的一個用于并行執(zhí)行任務(wù)的框架,是一個把大任務(wù)分割成若干個小任務(wù),最終匯總每個小任務(wù)結(jié)果后得到大任務(wù)結(jié)果的框架。
Fork/Join框架需要理解兩個點(diǎn),「分而治之」和「工作竊取算法」。
分而治之
以上Fork/Join框架的定義,就是分而治之思想的體現(xiàn)啦
工作竊取算法
把大任務(wù)拆分成小任務(wù),放到不同隊(duì)列執(zhí)行,交由不同的線程分別執(zhí)行時。有的線程優(yōu)先把自己負(fù)責(zé)的任務(wù)執(zhí)行完了,其他線程還在慢慢悠悠處理自己的任務(wù),這時候?yàn)榱顺浞痔岣咝?,就需要工作盜竊算法啦~
工作盜竊算法就是,「某個線程從其他隊(duì)列中竊取任務(wù)進(jìn)行執(zhí)行的過程」。一般就是指做得快的線程(盜竊線程)搶慢的線程的任務(wù)來做,同時為了減少鎖競爭,通常使用雙端隊(duì)列,即快線程和慢線程各在一端。
16. 聊聊ThreadLocal原理?
ThreadLocal的內(nèi)存結(jié)構(gòu)圖
為了對ThreadLocal有個宏觀的認(rèn)識,我們先來看下ThreadLocal的內(nèi)存結(jié)構(gòu)圖
從內(nèi)存結(jié)構(gòu)圖,我們可以看到:
Thread類中,有個ThreadLocal.ThreadLocalMap 的成員變量。
ThreadLocalMap內(nèi)部維護(hù)了Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型對象值。
關(guān)鍵源碼分析
對照著關(guān)鍵源碼來看,更容易理解一點(diǎn)哈~
首先看下Thread類的源碼,可以看到成員變量ThreadLocalMap的初始值是為null
?
?
public?class?Thread?implements?Runnable?{ ???//ThreadLocal.ThreadLocalMap是Thread的屬性 ???ThreadLocal.ThreadLocalMap?threadLocals?=?null; }
?
?
成員變量ThreadLocalMap的關(guān)鍵源碼如下:
?
?
static?class?ThreadLocalMap?{ ???? ????static?class?Entry?extends?WeakReference>?{ ????????/**?The?value?associated?with?this?ThreadLocal.?*/ ????????Object?value; ????????Entry(ThreadLocal>?k,?Object?v)?{ ????????????super(k); ????????????value?=?v; ????????} ????} ????//Entry數(shù)組 ????private?Entry[]?table; ???? ????//?ThreadLocalMap的構(gòu)造器,ThreadLocal作為key ????ThreadLocalMap(ThreadLocal>?firstKey,?Object?firstValue)?{ ????????table?=?new?Entry[INITIAL_CAPACITY]; ????????int?i?=?firstKey.threadLocalHashCode?&?(INITIAL_CAPACITY?-?1); ????????table[i]?=?new?Entry(firstKey,?firstValue); ????????size?=?1; ????????setThreshold(INITIAL_CAPACITY); ????} }
?
?
ThreadLocal類中的關(guān)鍵set()方法:
?
?
?public?void?set(T?value)?{ ????????Thread?t?=?Thread.currentThread();?//獲取當(dāng)前線程t ????????ThreadLocalMap?map?=?getMap(t);??//根據(jù)當(dāng)前線程獲取到ThreadLocalMap ????????if?(map?!=?null)??//如果獲取的ThreadLocalMap對象不為空 ????????????map.set(this,?value);?//K,V設(shè)置到ThreadLocalMap中 ????????else ????????????createMap(t,?value);?//創(chuàng)建一個新的ThreadLocalMap ????} ???? ?????ThreadLocalMap?getMap(Thread?t)?{ ???????return?t.threadLocals;?//返回Thread對象的ThreadLocalMap屬性 ????} ????void?createMap(Thread?t,?T?firstValue)?{?//調(diào)用ThreadLocalMap的構(gòu)造函數(shù) ????????t.threadLocals?=?new?ThreadLocalMap(this,?firstValue);?this表示當(dāng)前類ThreadLocal ????} ????
?
?
ThreadLocal類中的關(guān)鍵get()方法
?
?
????public?T?get()?{ ????????Thread?t?=?Thread.currentThread();//獲取當(dāng)前線程t ????????ThreadLocalMap?map?=?getMap(t);//根據(jù)當(dāng)前線程獲取到ThreadLocalMap ????????if?(map?!=?null)?{?//如果獲取的ThreadLocalMap對象不為空 ????????????//由this(即ThreadLoca對象)得到對應(yīng)的Value,即ThreadLocal的泛型值 ????????????ThreadLocalMap.Entry?e?=?map.getEntry(this); ????????????if?(e?!=?null)?{ ????????????????@SuppressWarnings("unchecked") ????????????????T?result?=?(T)e.value;? ????????????????return?result; ????????????} ????????} ????????return?setInitialValue();?//初始化threadLocals成員變量的值 ????} ???? ?????private?T?setInitialValue()?{ ????????T?value?=?initialValue();?//初始化value的值 ????????Thread?t?=?Thread.currentThread();? ????????ThreadLocalMap?map?=?getMap(t);?//以當(dāng)前線程為key,獲取threadLocals成員變量,它是一個ThreadLocalMap ????????if?(map?!=?null) ????????????map.set(this,?value);??//K,V設(shè)置到ThreadLocalMap中 ????????else ????????????createMap(t,?value);?//實(shí)例化threadLocals成員變量 ????????return?value; ????}
?
?
所以怎么回答ThreadLocal的實(shí)現(xiàn)原理 ?如下,最好是能結(jié)合以上結(jié)構(gòu)圖一起說明哈~
Thread線程類有一個類型為ThreadLocal.ThreadLocalMap的實(shí)例變量threadLocals,即每個線程都有一個屬于自己的ThreadLocalMap。
ThreadLocalMap內(nèi)部維護(hù)著Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
并發(fā)多線程場景下,每個線程Thread,在往ThreadLocal里設(shè)置值的時候,都是往自己的ThreadLocalMap里存,讀也是以某個ThreadLocal作為引用,在自己的map里找對應(yīng)的key,從而可以實(shí)現(xiàn)了線程隔離 。
17. TreadLocal為什么會導(dǎo)致內(nèi)存泄漏呢?
弱引用導(dǎo)致的內(nèi)存泄漏呢?
key是弱引用,GC回收會影響ThreadLocal的正常工作嘛?
ThreadLocal內(nèi)存泄漏的demo
17.1 弱引用導(dǎo)致的內(nèi)存泄漏呢?
我們先來看看TreadLocal的引用示意圖哈:
關(guān)于ThreadLocal內(nèi)存泄漏,網(wǎng)上比較流行的說法是這樣的:
ThreadLocalMap使用ThreadLocal的弱引用 作為key,當(dāng)ThreadLocal變量被手動設(shè)置為null,即一個ThreadLocal沒有外部強(qiáng)引用來引用它,當(dāng)系統(tǒng)GC時,ThreadLocal一定會被回收。這樣的話,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話(比如線程池的核心線程),這些key為null的Entry的value就會一直存在一條強(qiáng)引用鏈:Thread變量 -> Thread對象 -> ThreaLocalMap -> Entry -> value -> Object 永遠(yuǎn)無法回收,造成內(nèi)存泄漏。
當(dāng)ThreadLocal變量被手動設(shè)置為null后的引用鏈圖:
實(shí)際上,ThreadLocalMap的設(shè)計(jì)中已經(jīng)考慮到這種情況。所以也加上了一些防護(hù)措施:即在ThreadLocal的get,set,remove方法,都會清除線程ThreadLocalMap里所有key為null的value。
源代碼中,是有體現(xiàn)的,如ThreadLocalMap的set方法:
?
?
??private?void?set(ThreadLocal>?key,?Object?value)?{ ??????Entry[]?tab?=?table; ??????int?len?=?tab.length; ??????int?i?=?key.threadLocalHashCode?&?(len-1); ??????for?(Entry?e?=?tab[i]; ????????????e?!=?null; ????????????e?=?tab[i?=?nextIndex(i,?len)])?{ ??????????ThreadLocal>?k?=?e.get(); ??????????if?(k?==?key)?{ ??????????????e.value?=?value; ??????????????return; ??????????} ???????????//如果k等于null,則說明該索引位之前放的key(threadLocal對象)被回收了,這通常是因?yàn)橥獠繉hreadLocal變量置為null, ???????????//又因?yàn)閑ntry對threadLocal持有的是弱引用,一輪GC過后,對象被回收。 ????????????//這種情況下,既然用戶代碼都已經(jīng)將threadLocal置為null,那么也就沒打算再通過該對象作為key去取到之前放入threadLocalMap的value,?因此ThreadLocalMap中會直接替換調(diào)這種不新鮮的entry。 ??????????if?(k?==?null)?{ ??????????????replaceStaleEntry(key,?value,?i); ??????????????return; ??????????} ????????} ????????tab[i]?=?new?Entry(key,?value); ????????int?sz?=?++size; ????????//觸發(fā)一次Log2(N)復(fù)雜度的掃描,目的是清除過期Entry?? ????????if?(!cleanSomeSlots(i,?sz)?&&?sz?>=?threshold) ??????????rehash(); ????}
?
?
如ThreadLocal的get方法:
?
?
??public?T?get()?{ ????Thread?t?=?Thread.currentThread(); ????ThreadLocalMap?map?=?getMap(t); ????if?(map?!=?null)?{ ????????//去ThreadLocalMap獲取Entry,方法里面有key==null的清除邏輯 ????????ThreadLocalMap.Entry?e?=?map.getEntry(this); ????????if?(e?!=?null)?{ ????????????@SuppressWarnings("unchecked") ????????????T?result?=?(T)e.value; ????????????return?result; ????????} ????} ????return?setInitialValue(); } 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 ??????????//里面有key==null的清除邏輯 ??????????return?getEntryAfterMiss(key,?i,?e); ????} ???????? private?Entry?getEntryAfterMiss(ThreadLocal>?key,?int?i,?Entry?e)?{ ????????Entry[]?tab?=?table; ????????int?len?=?tab.length; ????????while?(e?!=?null)?{ ????????????ThreadLocal>?k?=?e.get(); ????????????if?(k?==?key) ????????????????return?e; ????????????//?Entry的key為null,則表明沒有外部引用,且被GC回收,是一個過期Entry ????????????if?(k?==?null) ????????????????expungeStaleEntry(i);?//刪除過期的Entry ????????????else ????????????????i?=?nextIndex(i,?len); ????????????e?=?tab[i]; ????????} ????????return?null; ????}
?
?
17.2 key是弱引用,GC回收會影響ThreadLocal的正常工作嘛?
有些小伙伴可能有疑問,ThreadLocal的key既然是弱引用 .會不會GC貿(mào)然把key回收掉,進(jìn)而影響ThreadLocal的正常使用?
弱引用 :具有弱引用的對象擁有更短暫的生命周期。如果一個對象只有弱引用存在了,則下次GC將會回收掉該對象 (不管當(dāng)前內(nèi)存空間足夠與否)
其實(shí)不會的,因?yàn)橛蠺hreadLocal變量引用著它,是不會被GC回收的,除非手動把ThreadLocal變量設(shè)置為null,我們可以跑個demo來驗(yàn)證一下:
?
?
??public?class?WeakReferenceTest?{ ????public?static?void?main(String[]?args)?{ ????????Object?object?=?new?Object(); ????????WeakReference
?
?
結(jié)論就是,小伙伴放下這個疑惑了,哈哈~
17.3 ThreadLocal內(nèi)存泄漏的demo
給大家來看下一個內(nèi)存泄漏的例子,其實(shí)就是用線程池,一直往里面放對象
?
?
public?class?ThreadLocalTestDemo?{ ????private?static?ThreadLocal?tianLuoThreadLocal?=?new?ThreadLocal<>(); ????public?static?void?main(String[]?args)?throws?InterruptedException?{ ????????ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(5,?5,?1,?TimeUnit.MINUTES,?new?LinkedBlockingQueue<>()); ????????for?(int?i?=?0;?i?10;?++i)?{ ????????????threadPoolExecutor.execute(new?Runnable()?{ ????????????????@Override ????????????????public?void?run()?{ ????????????????????System.out.println("創(chuàng)建對象:"); ????????????????????TianLuoClass?tianLuoClass?=?new?TianLuoClass(); ????????????????????tianLuoThreadLocal.set(tianLuoClass); ????????????????????tianLuoClass?=?null;?//將對象設(shè)置為?null,表示此對象不在使用了 ???????????????????//?tianLuoThreadLocal.remove(); ????????????????} ????????????}); ????????????Thread.sleep(1000); ????????} ????} ????static?class?TianLuoClass?{ ????????//?100M ????????private?byte[]?bytes?=?new?byte[100?*?1024?*?1024]; ????} } 創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: Exception?in?thread?"pool-1-thread-4"?java.lang.OutOfMemoryError:?Java?heap?space ?at?com.example.dto.ThreadLocalTestDemo$TianLuoClass. (ThreadLocalTestDemo.java:33) ?at?com.example.dto.ThreadLocalTestDemo$1.run(ThreadLocalTestDemo.java:21) ?at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ?at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ?at?java.lang.Thread.run(Thread.java:748)
?
?
運(yùn)行結(jié)果出現(xiàn)了OOM,tianLuoThreadLocal.remove();加上后,則不會OOM。
?
?
創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: 創(chuàng)建對象: ......
?
?
我們這里沒有手動設(shè)置 tianLuoThreadLocal變量為null,但是還是會內(nèi)存泄漏 。因?yàn)槲覀兪褂昧司€程池,線程池有很長的生命周期,因此線程池會一直持有tianLuoClass對象的value值,即使設(shè)置tianLuoClass = null;引用還是存在的。這就好像,你把一個個對象object放到一個list列表里,然后再單獨(dú)把object設(shè)置為null的道理是一樣的,列表的對象還是存在的。
?
?
????public?static?void?main(String[]?args)?{ ????????List
?
?
所以內(nèi)存泄漏就這樣發(fā)生啦,最后內(nèi)存是有限的,就拋出了OOM了。如果我們加上threadLocal.remove();,則不會內(nèi)存泄漏。為什么呢?因?yàn)閠hreadLocal.remove();會清除Entry,源碼如下:
?
?
????private?void?remove(ThreadLocal>?key)?{ ??????Entry[]?tab?=?table; ??????int?len?=?tab.length; ??????int?i?=?key.threadLocalHashCode?&?(len-1); ??????for?(Entry?e?=?tab[i]; ??????????e?!=?null; ??????????e?=?tab[i?=?nextIndex(i,?len)])?{ ??????????if?(e.get()?==?key)?{ ??????????????//清除entry ??????????????e.clear(); ????????????expungeStaleEntry(i); ????????????return; ????????} ????} }
?
?
18 為什么ThreadLocalMap 的 key 是弱引用,設(shè)計(jì)理念是?
通過閱讀ThreadLocal的源碼,我們是可以看到Entry的Key是設(shè)計(jì)為弱引用的(ThreadLocalMap使用ThreadLocal的弱引用作為Key的)。為什么要設(shè)計(jì)為弱引用呢?
我們先來回憶一下四種引用:
強(qiáng)引用 :我們平時new了一個對象就是強(qiáng)引用,例如 Object obj = new Object();即使在內(nèi)存不足的情況下,JVM寧愿拋出OutOfMemory錯誤也不會回收這種對象。
軟引用 :如果一個對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。
弱引用 :具有弱引用的對象擁有更短暫的生命周期。如果一個對象只有弱引用存在了,則下次GC將會回收掉該對象 (不管當(dāng)前內(nèi)存空間足夠與否)。
虛引用 :如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
我們先來看看官方文檔,為什么要設(shè)計(jì)為弱引用:
?
?
To?help?deal?with?very?large?and?long-lived?usages,?the?hash?table?entries?use?WeakReferences?for?keys. 為了應(yīng)對非常大和長時間的用途,哈希表使用弱引用的?key。
?
?
我再把ThreadLocal的引用示意圖搬過來:
下面我們分情況討論:
如果Key使用強(qiáng)引用:當(dāng)ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用的話,如果沒有手動刪除,ThreadLocal就不會被回收,會出現(xiàn)Entry的內(nèi)存泄漏問題。
如果Key使用弱引用:當(dāng)ThreadLocal的對象被回收了,因?yàn)門hreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value則在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。
因此可以發(fā)現(xiàn),使用弱引用作為Entry的Key,可以多一層保障:弱引用ThreadLocal不會輕易內(nèi)存泄漏,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。
實(shí)際上,我們的內(nèi)存泄漏的根本原因是,不再被使用的Entry,沒有從線程的ThreadLocalMap中刪除。一般刪除不再使用的Entry有這兩種方式:
一種就是,使用完ThreadLocal,手動調(diào)用remove(),把Entry從ThreadLocalMap中刪除
另外一種方式就是:ThreadLocalMap的自動清除機(jī)制去清除過期Entry.(ThreadLocalMap的get(),set()時都會觸發(fā)對過期Entry的清除)
19. 如何保證父子線程間的共享ThreadLocal數(shù)據(jù)
我們知道ThreadLocal是線程隔離的,如果我們希望父子線程共享數(shù)據(jù),如何做到呢?可以使用InheritableThreadLocal。先來看看demo:
?
?
public?class?InheritableThreadLocalTest?{ ???public?static?void?main(String[]?args)?{ ???????ThreadLocal?threadLocal?=?new?ThreadLocal<>(); ???????InheritableThreadLocal ?inheritableThreadLocal?=?new?InheritableThreadLocal<>(); ???????threadLocal.set("關(guān)注公眾號:芋道源碼"); ???????inheritableThreadLocal.set("關(guān)注公眾號:芋道源碼"); ???????Thread?thread?=?new?Thread(()->{ ???????????System.out.println("ThreadLocal?value?"?+?threadLocal.get()); ???????????System.out.println("InheritableThreadLocal?value?"?+?inheritableThreadLocal.get()); ???????}); ???????thread.start(); ??????? ???} } //運(yùn)行結(jié)果 ThreadLocal?value?null InheritableThreadLocal?value?關(guān)注公眾號:芋道源碼
?
?
可以發(fā)現(xiàn),在子線程中,是可以獲取到父線程的 InheritableThreadLocal 類型變量的值,但是不能獲取到 ThreadLocal 類型變量的值。
獲取不到ThreadLocal 類型的值,我們可以好理解,因?yàn)樗蔷€程隔離的嘛。InheritableThreadLocal 是如何做到的呢?原理是什么呢?
在Thread類中,除了成員變量threadLocals之外,還有另一個成員變量:inheritableThreadLocals。它們兩類型是一樣的:
?
?
public?class?Thread?implements?Runnable?{ ???ThreadLocalMap?threadLocals?=?null; ???ThreadLocalMap?inheritableThreadLocals?=?null; ?}
?
?
Thread類的init方法中,有一段初始化設(shè)置:
?
?
?private?void?init(ThreadGroup?g,?Runnable?target,?String?name, ??????????????????????long?stackSize,?AccessControlContext?acc, ??????????????????????boolean?inheritThreadLocals)?{ ?????? ????????...... ????????if?(inheritThreadLocals?&&?parent.inheritableThreadLocals?!=?null) ????????????this.inheritableThreadLocals?= ????????????????ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ????????/*?Stash?the?specified?stack?size?in?case?the?VM?cares?*/ ????????this.stackSize?=?stackSize; ????????/*?Set?thread?ID?*/ ????????tid?=?nextThreadID(); ????} ?static?ThreadLocalMap?createInheritedMap(ThreadLocalMap?parentMap)?{ ????????return?new?ThreadLocalMap(parentMap); ????}
?
?
可以發(fā)現(xiàn),當(dāng)parent的inheritableThreadLocals不為null時,就會將parent的inheritableThreadLocals,賦值給前線程的inheritableThreadLocals。說白了,就是如果當(dāng)前線程的inheritableThreadLocals不為null,就從父線程哪里拷貝過來一個過來,類似于另外一個ThreadLocal,但是數(shù)據(jù)從父線程那里來的。有興趣的小伙伴們可以在去研究研究源碼~
20. 如何保證多線程下 i++ 結(jié)果正確?
使用循環(huán)CAS,實(shí)現(xiàn)i++原子操作
使用鎖機(jī)制,實(shí)現(xiàn)i++原子操作
使用synchronized,實(shí)現(xiàn)i++原子操作
舉個簡單的例子,如下:
?
?
/** ?*??關(guān)注公眾號:芋道源碼? ?*??非常多干貨 ?*/ public?class?AtomicIntegerTest?{ ????private?static?AtomicInteger?atomicInteger?=?new?AtomicInteger(0); ????public?static?void?main(String[]?args)?throws?InterruptedException?{ ????????testIAdd(); ????} ????private?static?void?testIAdd()?throws?InterruptedException?{ ????????//創(chuàng)建線程池 ????????ExecutorService?executorService?=?Executors.newFixedThreadPool(2); ????????for?(int?i?=?0;?i?1000;?i++)?{ ????????????executorService.execute(()?->?{ ????????????????for?(int?j?=?0;?j?2;?j++)?{ ????????????????????//自增并返回當(dāng)前值 ????????????????????int?andIncrement?=?atomicInteger.incrementAndGet(); ????????????????????System.out.println("線程:"?+?Thread.currentThread().getName()?+?"?count="?+?andIncrement); ????????????????} ????????????}); ????????} ????????executorService.shutdown(); ????????Thread.sleep(100); ????????System.out.println("最終結(jié)果是?:"?+?atomicInteger.get()); ????} ???? }
?
?
運(yùn)行結(jié)果:
?
?
... 線程:pool-1-thread-1?count=1997 線程:pool-1-thread-1?count=1998 線程:pool-1-thread-1?count=1999 線程:pool-1-thread-2?count=315 線程:pool-1-thread-2?count=2000 最終結(jié)果是?:2000
?
?
21. 如何檢測死鎖?怎么預(yù)防死鎖?死鎖四個必要條件
死鎖是指多個線程因競爭資源而造成的一種互相等待的僵局。如圖感受一下:
死鎖的四個必要條件:
互斥:一次只有一個進(jìn)程可以使用一個資源。其他進(jìn)程不能訪問已分配給其他進(jìn)程的資源。
占有且等待:當(dāng)一個進(jìn)程在等待分配得到其他資源時,其繼續(xù)占有已分配得到的資源。
非搶占:不能強(qiáng)行搶占進(jìn)程中已占有的資源。
循環(huán)等待:存在一個封閉的進(jìn)程鏈,使得每個資源至少占有此鏈中下一個進(jìn)程所需要的一個資源。
如何預(yù)防死鎖?
加鎖順序(線程按順序辦事)
加鎖時限 (線程請求所加上權(quán)限,超時就放棄,同時釋放自己占有的鎖)
死鎖檢測
22. 如果線程過多,會怎樣?
使用多線程可以提升程序性能。但是如果使用過多的線程,則適得其反。
過多的線程會影響程序的系統(tǒng)。
一方面,線程的啟動和銷毀,都是需要開銷的。
其次,過多的并發(fā)線程也會導(dǎo)致共享有限資源的開銷增大。過多的線程,還會導(dǎo)致內(nèi)存泄漏,筆者在以前公司,看到一個生產(chǎn)問題:一個第三方的包是使用new Thread來實(shí)現(xiàn)的,使用完沒有恰當(dāng)回收銷毀,最后引發(fā)內(nèi)存泄漏問題。
因此,我們平時盡量使用線程池來管理線程。同時還需要設(shè)置恰當(dāng)?shù)木€程數(shù)。
23. 聊聊happens-before原則
在Java語言中,有一個先行發(fā)生原則(happens-before)。它包括八大規(guī)則,如下:
程序次序規(guī)則 :在一個線程內(nèi),按照控制流順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作。
管程鎖定規(guī)則 :一個unLock操作先行發(fā)生于后面對同一個鎖額lock操作
volatile變量規(guī)則 :對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作
線程啟動規(guī)則 :Thread對象的start()方法先行發(fā)生于此線程的每個一個動作
線程終止規(guī)則 :線程中所有的操作都先行發(fā)生于線程的終止檢測,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行
線程中斷規(guī)則 :對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生
對象終結(jié)規(guī)則 :一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始
傳遞性 :如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C
24. 如何實(shí)現(xiàn)兩個線程間共享數(shù)據(jù)
可以通過類變量直接將數(shù)據(jù)放到主存中
通過并發(fā)的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)
使用volatile變量或者鎖
調(diào)用atomic類(如AtomicInteger)
25. LockSupport作用是?
LockSupport是一個工具類。它的主要作用是掛起和喚醒線程 。該工具類是創(chuàng)建鎖和其他同步類的基礎(chǔ)。它的主要方法是
?
?
public?static?void?park(Object?blocker);?//?暫停指定線程 public?static?void?unpark(Thread?thread);?//?恢復(fù)指定的線程 public?static?void?park();?//?無期限暫停當(dāng)前線程
?
?
看個代碼的例子:
?
?
public?class?LockSupportTest?{ ????private?static?Object?object?=?new?Object(); ????static?MyThread?thread?=?new?MyThread("線程芋道源碼"); ????public?static?class?MyThread?extends?Thread?{ ????????public?MyThread(String?name)?{ ????????????super(name); ????????} ????????@Override?public?void?run()?{ ????????????synchronized?(object)?{ ????????????????System.out.println("線程名字:?"?+?Thread.currentThread()); ????????????????try?{ ????????????????????Thread.sleep(2000L); ????????????????}?catch?(InterruptedException?e)?{ ????????????????????e.printStackTrace(); ????????????????} ????????????????LockSupport.park(); ????????????????if?(Thread.currentThread().isInterrupted())?{ ????????????????????System.out.println("線程被中斷了"); ????????????????} ????????????????System.out.println("繼續(xù)執(zhí)行"); ????????????} ????????} ????} ????public?static?void?main(String[]?args)?{ ????????thread.start(); ????????LockSupport.unpark(thread); ????????System.out.println("恢復(fù)線程調(diào)用"); ????} } //output 恢復(fù)線程調(diào)用 線程名字:Thread[線程芋道源碼,5,main] 繼續(xù)執(zhí)行
?
?
因?yàn)閠hread線程內(nèi)部有休眠2秒的操作,所以unpark方法的操作肯定先于park方法的調(diào)用。為什么thread線程最終仍然可以結(jié)束,是因?yàn)閜ark和unpark會對每個線程維持一個許可證(布爾值)
26 ?線程池如何調(diào)優(yōu),如何確認(rèn)最佳線程數(shù)?
?
?
最佳線程數(shù)目?=?((線程等待時間+線程CPU時間)/線程CPU時間?)*?CPU數(shù)目
?
?
我們的服務(wù)器CPU核數(shù)為8核,一個任務(wù)線程cpu耗時為20ms,線程等待(網(wǎng)絡(luò)IO、磁盤IO)耗時80ms,那最佳線程數(shù)目:( 80 + 20 )/20 * 8 = 40。也就是設(shè)置 40個線程數(shù)最佳。
27. 為什么要用線程池?
線程池:一個管理線程的池子。線程池可以:
管理線程,避免增加創(chuàng)建線程和銷毀線程的資源損耗。
提高響應(yīng)速度。
重復(fù)利用線程。
28. Java的線程池執(zhí)行原理
線程池的執(zhí)行原理如下:
為了形象描述線程池執(zhí)行,打個比喻:
核心線程比作公司正式員工
非核心線程比作外包員工
阻塞隊(duì)列比作需求池
提交任務(wù)比作提需求
29. 聊聊線程池的核心參數(shù)
我們先來看看ThreadPoolExecutor的構(gòu)造函數(shù)
?
?
public?ThreadPoolExecutor(int?corePoolSize,?int?maximumPoolSize, ???long?keepAliveTime, ???TimeUnit?unit, ???BlockingQueue?workQueue, ???ThreadFactory?threadFactory, ???RejectedExecutionHandler?handler)?
?
?
corePoolSize:線程池核心線程數(shù)最大值
maximumPoolSize:線程池最大線程數(shù)大小
keepAliveTime:線程池中非核心線程空閑的存活時間大小
unit:線程空閑存活時間單位
workQueue:存放任務(wù)的阻塞隊(duì)列
threadFactory:用于設(shè)置創(chuàng)建線程的工廠,可以給創(chuàng)建的線程設(shè)置有意義的名字,可方便排查問題。
handler:線城池的飽和策略事件,主要有四種類型拒絕策略。
四種拒絕策略
AbortPolicy(拋出一個異常,默認(rèn)的)
DiscardPolicy(直接丟棄任務(wù))
DiscardOldestPolicy(丟棄隊(duì)列里最老的任務(wù),將當(dāng)前這個任務(wù)繼續(xù)提交給線程池)
CallerRunsPolicy(交給線程池調(diào)用所在的線程進(jìn)行處理)
幾種工作阻塞隊(duì)列
ArrayBlockingQueue(用數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列,按FIFO排序量)
LinkedBlockingQueue(基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,按FIFO排序任務(wù),容量可以選擇進(jìn)行設(shè)置,不設(shè)置的話,將是一個無邊界的阻塞隊(duì)列)
DelayQueue(一個任務(wù)定時周期的延遲執(zhí)行的隊(duì)列)
PriorityBlockingQueue(具有優(yōu)先級的無界阻塞隊(duì)列)
SynchronousQueue(一個不存儲元素的阻塞隊(duì)列,每個插入操作必須等到另一個線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài))
30.當(dāng)提交新任務(wù)時,異常如何處理?
我們先來看一段代碼:
?
?
??ExecutorService?threadPool?=?Executors.newFixedThreadPool(5); ??for?(int?i?=?0;?i?5;?i++)?{ ??????threadPool.submit(()?->?{ ??????????System.out.println("current?thread?name"?+?Thread.currentThread().getName()); ??????????Object?object?=?null; ??????????System.out.print("result##?"+object.toString()); ??????}); ??}
?
?
顯然,這段代碼會有異常,我們再來看看執(zhí)行結(jié)果
雖然沒有結(jié)果輸出,但是沒有拋出異常,所以我們無法感知任務(wù)出現(xiàn)了異常,所以需要添加try/catch。如下圖:
OK,線程的異常處理,我們可以直接try...catch捕獲。
31. AQS組件,實(shí)現(xiàn)原理
AQS,即AbstractQueuedSynchronizer,是構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架,它使用了一個int成員變量表示同步狀態(tài),通過內(nèi)置的FIFO隊(duì)列來完成資源獲取線程的排隊(duì)工作??梢曰卮鹨韵逻@幾個關(guān)鍵點(diǎn)哈:
state 狀態(tài)的維護(hù)。
CLH隊(duì)列
ConditionObject通知
模板方法設(shè)計(jì)模式
獨(dú)占與共享模式。
自定義同步器。
AQS全家桶的一些延伸,如:ReentrantLock等。
31.1 state 狀態(tài)的維護(hù)
state,int變量,鎖的狀態(tài),用volatile修飾,保證多線程中的可見性。
getState()和setState()方法采用final修飾,限制AQS的子類重寫它們兩。
compareAndSetState()方法采用樂觀鎖思想的CAS算法操作確保線程安全,保證狀態(tài) 設(shè)置的原子性。
31.2 CLH隊(duì)列
CLH 同步隊(duì)列,全英文Craig, Landin, and Hagersten locks。是一個FIFO雙向隊(duì)列,其內(nèi)部通過節(jié)點(diǎn)head和tail記錄隊(duì)首和隊(duì)尾元素,隊(duì)列元素的類型為Node。AQS依賴它來完成同步狀態(tài)state的管理,當(dāng)前線程如果獲取同步狀態(tài)失敗時,AQS則會將當(dāng)前線程已經(jīng)等待狀態(tài)等信息構(gòu)造成一個節(jié)點(diǎn)(Node)并將其加入到CLH同步隊(duì)列,同時會阻塞當(dāng)前線程,當(dāng)同步狀態(tài)釋放時,會把首節(jié)點(diǎn)喚醒(公平鎖),使其再次嘗試獲取同步狀態(tài)。
31.3 ConditionObject通知
我們都知道,synchronized控制同步的時候,可以配合Object的wait()、notify(),notifyAll() 系列方法可以實(shí)現(xiàn)等待/通知模式。而Lock呢?它提供了條件Condition接口,配合await(),signal(),signalAll() 等方法也可以實(shí)現(xiàn)等待/通知機(jī)制。ConditionObject實(shí)現(xiàn)了Condition接口,給AQS提供條件變量的支持
ConditionObject隊(duì)列與CLH隊(duì)列的愛恨情仇:
調(diào)用了await()方法的線程,會被加入到conditionObject等待隊(duì)列中,并且喚醒CLH隊(duì)列中head節(jié)點(diǎn)的下一個節(jié)點(diǎn)。
線程在某個ConditionObject對象上調(diào)用了singnal()方法后,等待隊(duì)列中的firstWaiter會被加入到AQS的CLH隊(duì)列中,等待被喚醒。
當(dāng)線程調(diào)用unLock()方法釋放鎖時,CLH隊(duì)列中的head節(jié)點(diǎn)的下一個節(jié)點(diǎn)(在本例中是firtWaiter),會被喚醒。
31.4 模板方法設(shè)計(jì)模式
模板方法模式:在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。
AQS的典型設(shè)計(jì)模式就是模板方法設(shè)計(jì)模式啦。AQS全家桶(ReentrantLock,Semaphore)的衍生實(shí)現(xiàn),就體現(xiàn)出這個設(shè)計(jì)模式。如AQS提供tryAcquire,tryAcquireShared等模板方法,給子類實(shí)現(xiàn)自定義的同步器。
31.5 獨(dú)占與共享模式。
獨(dú)占式: 同一時刻僅有一個線程持有同步狀態(tài),如ReentrantLock。又可分為公平鎖和非公平鎖。
共享模式:多個線程可同時執(zhí)行,如Semaphore/CountDownLatch等都是共享式的產(chǎn)物。
31.6 自定義同步器
你要實(shí)現(xiàn)自定義鎖的話,首先需要確定你要實(shí)現(xiàn)的是獨(dú)占鎖還是共享鎖,定義原子變量state的含義,再定義一個內(nèi)部類去繼承AQS,重寫對應(yīng)的模板方法即可啦
32 Semaphore原理
Semaphore ,我們也把它叫做信號量 ??梢杂脕砜刂仆瑫r訪問特定資源的線程數(shù)量 ,通過協(xié)調(diào)各個線程,以保證合理的使用資源。
我們可以把它簡單的理解成我們停車場入口立著的那個顯示屏,每當(dāng)有一輛車進(jìn)入停車場顯示屏就會顯示剩余車位減1,每有一輛車從停車場出去,顯示屏上顯示的剩余車輛就會加1,當(dāng)顯示屏上的剩余車位為0時,停車場入口的欄桿就不會再打開,車輛就無法進(jìn)入停車場了,直到有一輛車從停車場出去為止。
32.1 Semaphore使用demo
我們就以停車場的例子,來實(shí)現(xiàn)demo。
假設(shè)停車場最多可以停20輛車,現(xiàn)在有100輛要進(jìn)入停車場。
我們很容易寫出以下代碼;
?
?
public?class?SemaphoreTest?{ ????private??static?Semaphore?semaphore=new?Semaphore(20); ????public?static?void?main(String[]?args)?{ ????????? ????????ExecutorService?executorService=?Executors.newFixedThreadPool(200); ????????//模擬100輛車要來 ????????for?(int?i?=?0;?i?100;?i++)?{ ????????????executorService.execute(()->{ ????????????????System.out.println("===="+Thread.currentThread().getName()+"準(zhǔn)備進(jìn)入停車場=="); ????????????????//車位判斷 ????????????????if?(semaphore.availablePermits()?==?0)?{ ????????????????????System.out.println("車輛不足,請耐心等待"); ????????????????} ????????????????try?{ ????????????????????//獲取令牌嘗試進(jìn)入停車場 ????????????????????semaphore.acquire(); ????????????????????System.out.println("===="?+?Thread.currentThread().getName()?+?"成功進(jìn)入停車場"); ????????????????????//模擬車輛在停車場停留的時間 ????????????????????Thread.sleep(new?Random().nextInt(20000)); ????????????????????System.out.println("===="?+?Thread.currentThread().getName()?+?"駛出停車場"); ????????????????????//釋放令牌,騰出停車場車位 ????????????????????semaphore.release(); ?????????????????}?catch?(InterruptedException?e)?{ ????????????????????e.printStackTrace(); ????????????????} ????????????}); ????????????//線程池關(guān)閉?????????? ????????????executorService.shutdown(); ????????} ????} }
?
?
32.2 Semaphore原理
我們來看下實(shí)現(xiàn)的原理是怎樣的。
Semaphore構(gòu)造函數(shù)
可用令牌數(shù)
獲取令牌
釋放令牌
Semaphore構(gòu)造函數(shù)
?
?
Semaphore?semaphore=new?Semaphore(20);
?
?
它會創(chuàng)建一個非公平的鎖的同步阻塞隊(duì)列,并且把初始令牌數(shù)量(20)賦值給同步隊(duì)列的state,這個state就是AQS的哈。
?
?
?//構(gòu)造函數(shù),創(chuàng)建一個非公平的鎖的同步阻塞隊(duì)列 ?public?Semaphore(int?permits)?{ ????sync?=?new?NonfairSync(permits); } ???? NonfairSync(int?permits)?{ ????super(permits); } //把令牌數(shù)量賦值給同步隊(duì)列的state Sync(int?permits)?{ ????setState(permits); }
?
?
2.可用令牌數(shù)
這個availablePermits,獲取的就是state值。剛開始為20,所以肯定不會為0嘛。
?
?
semaphore.availablePermits(); public?int?availablePermits()?{ ??return?sync.getPermits(); } final?int?getPermits()?{ ??return?getState(); }
?
?
獲取令牌
接著我們再看下獲取令牌的API
?
?
semaphore.acquire();
?
?
獲取1個令牌
?
?
??public?void?acquire()?throws?InterruptedException?{ ????sync.acquireSharedInterruptibly(1); ??} ?? ????public?final?void?acquireSharedInterruptibly(int?arg) ????????throws?InterruptedException?{ ????if?(Thread.interrupted()) ????????throw?new?InterruptedException(); ??????//嘗試獲取令牌,arg為獲取令牌個數(shù) ????if?(tryAcquireShared(arg)?0) ????????// ????????doAcquireSharedInterruptibly(arg); ??}
?
?
嘗試獲取令牌,使用了CAS算法。
?
?
final?int?nonfairTryAcquireShared(int?acquires)?{ ????????????for?(;;)?{ ????????????????int?available?=?getState(); ????????????????int?remaining?=?available?-?acquires; ????????????????if?(remaining?0?|| ????????????????????compareAndSetState(available,?remaining)) ????????????????????return?remaining; ????????????} ????????}
?
?
可獲取令牌的話,就創(chuàng)建節(jié)點(diǎn),加入阻塞隊(duì)列;重雙向鏈表的head,tail節(jié)點(diǎn)關(guān)系,清空無效節(jié)點(diǎn);掛起當(dāng)前節(jié)點(diǎn)線程
?
?
????private?void?doAcquireSharedInterruptibly(int?arg) ????????throws?InterruptedException?{ ????????//創(chuàng)建節(jié)點(diǎn)加入阻塞隊(duì)列 ????????final?Node?node?=?addWaiter(Node.SHARED); ????????boolean?failed?=?true; ????????try?{ ????????????for?(;;)?{ ????????????????final?Node?p?=?node.predecessor(); ????????????????if?(p?==?head)?{ ????????????????????//返回鎖的state ????????????????????int?r?=?tryAcquireShared(arg); ????????????????????if?(r?>=?0)?{ ????????????????????????setHeadAndPropagate(node,?r); ????????????????????????p.next?=?null;?//?help?GC ????????????????????????failed?=?false; ????????????????????????return; ????????????????????} ????????????????} ????????????????//重組雙向鏈表,清空無效節(jié)點(diǎn),掛起當(dāng)前線程 ????????????????if?(shouldParkAfterFailedAcquire(p,?node)?&& ????????????????????parkAndCheckInterrupt()) ????????????????????throw?new?InterruptedException(); ????????????} ????????}?finally?{ ????????????if?(failed) ????????????????cancelAcquire(node); ????????} ????}
?
?
釋放令牌
?
?
?semaphore.release(); ? ??/** ?????*?釋放令牌 ?????*/ public?void?release()?{ ????sync.releaseShared(1); } ??public?final?boolean?releaseShared(int?arg)?{ ?????????//釋放共享鎖 ????????if?(tryReleaseShared(arg))?{ ????????????//喚醒所有共享節(jié)點(diǎn)線程 ????????????doReleaseShared(); ????????????return?true; ????????} ????????return?false; ????}
?
?
3 synchronized做了哪些優(yōu)化?什么是偏向鎖?什么是自旋鎖?鎖租化?
在JDK1.6之前,synchronized的實(shí)現(xiàn)直接調(diào)用ObjectMonitor的enter和exit,這種鎖被稱之為重量級鎖。從JDK6開始,HotSpot虛擬機(jī)開發(fā)團(tuán)隊(duì)對Java中的鎖進(jìn)行優(yōu)化,如增加了適應(yīng)性自旋、鎖消除、鎖粗化、輕量級鎖和偏向鎖等優(yōu)化策略 ,提升了synchronized的性能。
偏向鎖:在無競爭的情況下,只是在Mark Word里存儲當(dāng)前線程指針,CAS操作都不做。
輕量級鎖:在沒有多線程競爭時,相對重量級鎖,減少操作系統(tǒng)互斥量帶來的性能消耗。但是,如果存在鎖競爭,除了互斥量本身開銷,還額外有CAS操作的開銷。
自旋鎖:減少不必要的CPU上下文切換。在輕量級鎖升級為重量級鎖時,就使用了自旋加鎖的方式
鎖粗化:將多個連續(xù)的加鎖、解鎖操作連接在一起,擴(kuò)展成一個范圍更大的鎖。
鎖消除:虛擬機(jī)即時編譯器在運(yùn)行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進(jìn)行消除。
34 什么是上下文切換?
什么是CPU上下文?
CPU 寄存器,是CPU內(nèi)置的容量小、但速度極快的內(nèi)存。而程序計(jì)數(shù)器,則是用來存儲 CPU 正在執(zhí)行的指令位置、或者即將執(zhí)行的下一條指令位置。它們都是 CPU 在運(yùn)行任何任務(wù)前,必須的依賴環(huán)境,因此叫做CPU上下文。
什么是CPU上下文切換?
它是指,先把前一個任務(wù)的CPU上下文(也就是CPU寄存器和程序計(jì)數(shù)器)保存起來,然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置,運(yùn)行新任務(wù)。
一般我們說的上下文切換 ,就是指內(nèi)核(操作系統(tǒng)的核心)在CPU上對進(jìn)程或者線程進(jìn)行切換 。進(jìn)程從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變,需要通過系統(tǒng)調(diào)用來完成。系統(tǒng)調(diào)用的過程,會發(fā)生CPU上下文的切換。
所以大家有時候會聽到這種說法,線程的上下文切換 。它指,CPU資源的分配采用了時間片輪轉(zhuǎn) ,即給每個線程分配一個時間片,線程在時間片內(nèi)占用 CPU 執(zhí)行任務(wù)。當(dāng)線程使用完時間片后,就會處于就緒狀態(tài)并讓出 CPU 讓其他線程占用,這就是線程的上下文切換??磦€圖,可能會更容易理解一點(diǎn)
35.為什么wait(),notify(),notifyAll()在對象中,而不在Thread類中
鎖只是個一個標(biāo)記,存在對象頭里面。
下面從面向?qū)ο蠛陀^察者模式角度來分析。
面向?qū)ο蟮慕嵌龋何覀兛梢园褀ait和notify直接理解為get和set方法。wait和notify方法都是對對象的鎖進(jìn)行操作,那么自然這些方法應(yīng)該屬于對象。舉例來說,門對象上有鎖屬性,開鎖和關(guān)鎖的方法應(yīng)該屬于門對象,而不應(yīng)該屬于人對象。
從觀察者模式的角度:對象是被觀察者,線程是觀察者。被觀察者的狀態(tài)如果發(fā)生變化,理應(yīng)有被觀察者去輪詢通知觀察者,否則的話,觀察者怎么知道notify方法應(yīng)該在哪個時刻調(diào)用?n個觀察者的notify又如何做到同時調(diào)用?
來源:知乎 ?https://www.zhihu.com/question/321674476
36. 線程池中 submit()和 execute()方法有什么區(qū)別?
execute和submit都屬于線程池的方法,execute只能提交Runnable類型的任務(wù),而submit既能提交Runnable類型任務(wù)也能提交Callable類型任務(wù)。
execute會直接拋出任務(wù)執(zhí)行時的異常,submit會吃掉異常,可通過Future的get方法將任務(wù)執(zhí)行時的異常重新拋出。
execute所屬頂層接口是Executor,submit所屬頂層接口是ExecutorService,實(shí)現(xiàn)類ThreadPoolExecutor重寫了execute方法,抽象類AbstractExecutorService重寫了submit方法。
37 AtomicInteger 的原理?
AtomicInteger的底層,是基于CAS實(shí)現(xiàn)的。我們可以看下AtomicInteger的添加方法。如下
?
?
????public?final?int?getAndIncrement()?{ ????????return?unsafe.getAndAddInt(this,?valueOffset,?1); ????} ????通過Unsafe類的實(shí)例來進(jìn)行添加操作 ????public?final?int?getAndAddInt(Object?var1,?long?var2,?int?var4)?{ ????????int?var5; ????????do?{ ????????????var5?=?this.getIntVolatile(var1,?var2); ????????}?while(!this.compareAndSwapInt(var1,?var2,?var5,?var5?+?var4));//使用了CAS算法實(shí)現(xiàn) ????????return?var5; ????}
?
?
注意:compareAndSwapInt是一個native方法哈,它是基于CAS來操作int類型的變量。并且,其它的原子操作類基本也大同小異。
38 Java中用到的線程調(diào)度算法是什么?
我們知道有兩種調(diào)度模型:分時調(diào)度和搶占式調(diào)度 。
分時調(diào)度模型:讓所有的線程輪流獲得cpu的使用權(quán),并且平均分配每個線程占用的 CPU 的時間片。
搶占式調(diào)度:優(yōu)先讓可運(yùn)行池中優(yōu)先級高的線程占用CPU,如果可運(yùn)行池中的線程優(yōu)先級相同,那么就隨機(jī)選擇一個線程,使其占用CPU。處于運(yùn)行狀態(tài)的線程會一直運(yùn)行,直至它不得不放棄 CPU。
Java默認(rèn)的線程調(diào)度算法是搶占式。即線程用完CPU之后,操作系統(tǒng)會根據(jù)線程優(yōu)先級、線程饑餓情況等數(shù)據(jù)算出一個總的優(yōu)先級并分配下一個時間片給某個線程執(zhí)行。
39. shutdown() 和 shutdownNow()的區(qū)別
shutdownNow()能立即停止線程池,正在跑的和正在等待的任務(wù)都停下了。這樣做立即生效,但是風(fēng)險(xiǎn)也比較大。
shutdown()只是關(guān)閉了提交通道,用submit()是無效的;而內(nèi)部的任務(wù)該怎么跑還是怎么跑,跑完再徹底停止線程池。
40 說說幾種常見的線程池及使用場景?
幾種常用線程池:
newFixedThreadPool (固定數(shù)目線程的線程池)
newCachedThreadPool(可緩存線程的線程池)
newSingleThreadExecutor(單線程的線程池)
newScheduledThreadPool(定時及周期執(zhí)行的線程池)
40.1 newFixedThreadPool
?
?
??public?static?ExecutorService?newFixedThreadPool(int?nThreads,?ThreadFactory?threadFactory)?{ ????????return?new?ThreadPoolExecutor(nThreads,?nThreads, ??????????????????????????????????????0L,?TimeUnit.MILLISECONDS, ??????????????????????????????????????new?LinkedBlockingQueue(), ??????????????????????????????????????threadFactory); ????}
?
?
核心線程數(shù)和最大線程數(shù)大小一樣
沒有所謂的非空閑時間,即keepAliveTime為0
阻塞隊(duì)列為無界隊(duì)列LinkedBlockingQueue
使用場景
FixedThreadPool 適用于處理CPU密集型的任務(wù),確保CPU在長期被工作線程使用的情況下,盡可能的少的分配線程,即適用執(zhí)行長期的任務(wù)。
40.2 newCachedThreadPool
?
?
?public?static?ExecutorService?newCachedThreadPool(ThreadFactory?threadFactory)?{ ????????return?new?ThreadPoolExecutor(0,?Integer.MAX_VALUE, ??????????????????????????????????????60L,?TimeUnit.SECONDS, ??????????????????????????????????????new?SynchronousQueue(), ??????????????????????????????????????threadFactory); ????}
?
?
核心線程數(shù)為0
最大線程數(shù)為Integer.MAX_VALUE
阻塞隊(duì)列是SynchronousQueue
非核心線程空閑存活時間為60秒
使用場景
當(dāng)提交任務(wù)的速度大于處理任務(wù)的速度時,每次提交一個任務(wù),就必然會創(chuàng)建一個線程。極端情況下會創(chuàng)建過多的線程,耗盡 CPU 和內(nèi)存資源。由于空閑 60 秒的線程會被終止,長時間保持空閑的 CachedThreadPool 不會占用任何資源。
40.3 newSingleThreadExecutor 單線程的線程池
?
?
??public?static?ExecutorService?newSingleThreadExecutor(ThreadFactory?threadFactory)?{ ????????return?new?FinalizableDelegatedExecutorService ????????????(new?ThreadPoolExecutor(1,?1, ????????????????????????????????????0L,?TimeUnit.MILLISECONDS, ????????????????????????????????????new?LinkedBlockingQueue(), ????????????????????????????????????threadFactory)); ????}
?
?
核心線程數(shù)為1
最大線程數(shù)也為1
阻塞隊(duì)列是LinkedBlockingQueue
keepAliveTime為0
使用場景
適用于串行執(zhí)行任務(wù)的場景,一個任務(wù)一個任務(wù)地執(zhí)行。
40.4 newScheduledThreadPool
?
?
??public?ScheduledThreadPoolExecutor(int?corePoolSize)?{ ????????super(corePoolSize,?Integer.MAX_VALUE,?0,?NANOSECONDS, ??????????????new?DelayedWorkQueue()); ????}
?
?
最大線程數(shù)為Integer.MAX_VALUE
阻塞隊(duì)列是DelayedWorkQueue
keepAliveTime為0
scheduleAtFixedRate() :按某種速率周期執(zhí)行
scheduleWithFixedDelay():在某個延遲后執(zhí)行
使用場景
周期性執(zhí)行任務(wù)的場景,需要限制線程數(shù)量的場景
41 什么是FutureTask
FutureTask是一種可以取消的異步的計(jì)算任務(wù)。它的計(jì)算是通過Callable實(shí)現(xiàn)的,可以把它理解為是可以返回結(jié)果的Runnable。
使用FutureTask的優(yōu)點(diǎn):
可以獲取線程執(zhí)行后的返回結(jié)果;
提供了超時控制功能。
它實(shí)現(xiàn)了Runnable接口和Future接口,底層基于生產(chǎn)者消費(fèi)者模式實(shí)現(xiàn)。
FutureTask用于在異步操作場景中,F(xiàn)utureTask作為生產(chǎn)者(執(zhí)行FutureTask的線程)和消費(fèi)者(獲取FutureTask結(jié)果的線程)的橋梁,如果生產(chǎn)者先生產(chǎn)出了數(shù)據(jù),那么消費(fèi)者get時能會直接拿到結(jié)果;如果生產(chǎn)者還未產(chǎn)生數(shù)據(jù),那么get時會一直阻塞或者超時阻塞,一直到生產(chǎn)者產(chǎn)生數(shù)據(jù)喚醒阻塞的消費(fèi)者為止。
42 java中interrupt(),interrupted()和 isInterrupted()的區(qū)別
interrupt 它是真正觸發(fā)中斷的方法。
interrupted是Thread中的一個類方法,它也調(diào)用了isInterrupted(true)方法,不過它傳遞的參數(shù)是true,表示將會清除中斷標(biāo)志位。
isInterrupted是Thread類中的一個實(shí)例方法,可以判斷實(shí)例線程是否被中斷。。
?
?
????public?void?interrupt()?{ ????????if?(this?!=?Thread.currentThread()) ????????????checkAccess(); ????????synchronized?(blockerLock)?{ ????????????Interruptible?b?=?blocker; ????????????if?(b?!=?null)?{ ????????????????interrupt0();???????????//?Just?to?set?the?interrupt?flag ????????????????b.interrupt(this); ????????????????return; ????????????} ????????} ????????interrupt0(); ????} ???? ???public?static?boolean?interrupted()?{ ????????return?currentThread().isInterrupted(true); ????} ????public?boolean?isInterrupted()?{ ????????return?isInterrupted(false); ????}
?
?
43 有三個線程T1,T2,T3, 怎么確保它們按順序執(zhí)行
可以使用join方法解決這個問題。比如在線程A中,調(diào)用線程B的join方法表示的意思就是 :A等待B線程執(zhí)行完畢后(釋放CPU執(zhí)行權(quán)),在繼續(xù)執(zhí)行。
代碼如下:
?
?
public?class?ThreadTest?{ ????public?static?void?main(String[]?args)?{ ????????Thread?spring?=?new?Thread(new?SeasonThreadTask("春天")); ????????Thread?summer?=?new?Thread(new?SeasonThreadTask("夏天")); ????????Thread?autumn?=?new?Thread(new?SeasonThreadTask("秋天")); ????????try ????????{ ????????????//春天線程先啟動 ????????????spring.start(); ????????????//主線程等待線程spring執(zhí)行完,再往下執(zhí)行 ????????????spring.join(); ????????????//夏天線程再啟動 ????????????summer.start(); ????????????//主線程等待線程summer執(zhí)行完,再往下執(zhí)行 ????????????summer.join(); ????????????//秋天線程最后啟動 ????????????autumn.start(); ????????????//主線程等待線程autumn執(zhí)行完,再往下執(zhí)行 ????????????autumn.join(); ????????}?catch?(InterruptedException?e) ????????{ ????????????e.printStackTrace(); ????????} ????} } class?SeasonThreadTask?implements?Runnable{ ????private?String?name; ????public?SeasonThreadTask(String?name){ ????????this.name?=?name; ????} ????@Override ????public?void?run()?{ ????????for?(int?i?=?1;?i?<4;?i++)?{ ????????????System.out.println(this.name?+?"來了:?"?+?i?+?"次"); ????????????try?{ ????????????????Thread.sleep(100); ????????????}?catch?(InterruptedException?e)?{ ????????????????e.printStackTrace(); ????????????} ????????} ????} } 運(yùn)行結(jié)果: 春天來了:?1次 春天來了:?2次 春天來了:?3次 夏天來了:?1次 夏天來了:?2次 夏天來了:?3次 秋天來了:?1次 秋天來了:?2次 秋天來了:?3次
?
?
44 有哪些阻塞隊(duì)列
ArrayBlockingQueue ? ?一個由數(shù)組構(gòu)成的有界阻塞隊(duì)列
LinkedBlockingQueue ? ?一個由鏈表構(gòu)成的有界阻塞隊(duì)列
PriorityBlockingQueue ? 一個支持優(yōu)先級排序的無界阻塞隊(duì)列
DelayQueue ? ? ? ?一個使用優(yōu)先隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列。
SynchroniouQueue ? ? 一個不儲存元素的阻塞隊(duì)列
LinkedTransferQueue ? ?一個由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列
LinkedBlockingDeque ? ?一個由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列
45 Java 中 ConcurrentHashMap 的并發(fā)度是什么?
并發(fā)度就是segment的個數(shù),通常是2的N次方。默認(rèn)是16
46 Java線程有哪些常用的調(diào)度方法?
46.1 線程休眠
Thread.sleep(long)方法,使線程轉(zhuǎn)到超時等待阻塞(TIMED_WAITING) 狀態(tài)。long參數(shù)設(shè)定睡眠的時間,以毫秒為單位。當(dāng)睡眠結(jié)束后,線程自動轉(zhuǎn)為就緒(Runnable)狀態(tài)。
46.2 線程中斷
interrupt()表示中斷線程。需要注意的是,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的。對某一線程調(diào)用interrupt()時,如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會拋出InterruptedException。但是,一旦該線程進(jìn)入到wait()/sleep()/join()后,就會立刻拋出InterruptedException??梢杂胕sInterrupted()來獲取狀態(tài)。
46.3 線程等待
Object類中的wait()方法,會導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的notify()方法或notifyAll()喚醒方法。
46.4 線程讓步
Thread.yield()方法,暫停當(dāng)前正在執(zhí)行的線程對象,把執(zhí)行機(jī)會讓給相同或者更高優(yōu)先級的線程。
46.5 線程通知
Object的notify()方法,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實(shí)現(xiàn)做出決定時發(fā)生。
notifyAll(),則是喚醒在此對象監(jiān)視器上等待的所有線程。
47. ReentrantLock的加鎖原理
ReentrantLock,是可重入鎖,是JDK5中添加在并發(fā)包下的一個高性能的工具。它支持同一個線程在未釋放鎖的情況下重復(fù)獲取鎖。
47.1 ReentrantLock使用的模板
我們先來看下是ReentrantLock使用的模板:
?
?
???//實(shí)例化對象 ????ReentrantLock?lock?=?new?ReentrantLock(); ????//獲取鎖操作 ????lock.lock(); ????try?{ ????????//?執(zhí)行業(yè)務(wù)代碼邏輯 ????}?catch?(Exception?ex)?{ ????????//異常處理 ????}?finally?{ ????????//?解鎖操作 ????????lock.unlock(); ????}
?
?
47.2 什么是非公平鎖,什么是公平鎖?
ReentrantLock無參構(gòu)造函數(shù),默認(rèn)創(chuàng)建的是非公平鎖 ,如下:
?
?
public?ReentrantLock()?{ ????sync?=?new?NonfairSync(); }
?
?
而通過fair參數(shù)指定使用公平鎖(FairSync)還是非公平鎖(NonfairSync)
?
?
public?ReentrantLock(boolean?fair)?{ ????sync?=?fair???new?FairSync()?:?new?NonfairSync(); }
?
?
什么是公平鎖?
公平鎖:多個線程按照申請鎖的順序去獲得鎖,線程會直接進(jìn)入隊(duì)列去排隊(duì),永遠(yuǎn)都是隊(duì)列的第一位才能得到鎖。
優(yōu)點(diǎn):所有的線程都能得到資源,不會餓死在隊(duì)列中。
缺點(diǎn):吞吐量會下降很多,隊(duì)列里面除了第一個線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷會很大。
什么是非公平鎖?
非公平鎖:多個線程去獲取鎖的時候,會直接去嘗試獲取,獲取不到,再去進(jìn)入等待隊(duì)列,如果能獲取到,就直接獲取到鎖。
優(yōu)點(diǎn):可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點(diǎn),CPU也不必取喚醒所有線程,會減少喚起線程的數(shù)量。
缺點(diǎn):你們可能也發(fā)現(xiàn)了,這樣可能導(dǎo)致隊(duì)列中間的線程一直獲取不到鎖或者長時間獲取不到鎖,導(dǎo)致餓死。
47.3 lock()加鎖流程
大家可以結(jié)合AQS + 公平鎖/非公平鎖 + CAS去講ReentrantLock的原理哈。
48. 線程間的通訊方式
48.1 volatile和synchronized關(guān)鍵字
volatile關(guān)鍵字用來修飾共享變量,保證了共享變量的可見性,任何線程需要讀取時都要到內(nèi)存中讀?。ù_保獲得最新值)。
synchronized關(guān)鍵字確保只能同時有一個線程訪問方法或者變量,保證了線程訪問的可見性和排他性。
48.2 等待/通知機(jī)制
等待/通知機(jī)制,是指一個線程A調(diào)用了對象O的wait()方法進(jìn)入等待狀態(tài),而另一個線程B 調(diào)用了對象O的notify()或者notifyAll()方法,線程A收到通知后從對象O的wait()方法返回,進(jìn)而 執(zhí)行后續(xù)操作。
48.3 管道輸入/輸出流
管道輸入/輸出流和普通的文件輸入/輸出流或者網(wǎng)絡(luò)輸入/輸出流不同之處在于,它主要 用于線程之間的數(shù)據(jù)傳輸,而傳輸?shù)拿浇闉閮?nèi)存。
管道輸入/輸出流主要包括了如下4種具體實(shí)現(xiàn):PipedOutputStream、PipedInputStream、 PipedReader和PipedWriter,前兩種面向字節(jié),而后兩種面向字符。
48.4 join()方法
如果一個線程A執(zhí)行了thread.join()語句,其含義是:當(dāng)前線程A等待thread線程終止之后才 從thread.join()返回。線程Thread除了提供join()方法之外,還提供了join(long millis)和join(long millis,int nanos)兩個具備超時特性的方法。這兩個超時方法表示,如果線程thread在給定的超時 時間里沒有終止,那么將會從該超時方法中返回。
48.5 ThreadLocal
ThreadLocal,即線程本地變量(每個線程都有自己唯一的一個哦),是一個以ThreadLocal對象為鍵、任意對象為值的存儲結(jié)構(gòu)。底層是一個ThreadLocalMap來存儲信息,key是弱引用,value是強(qiáng)引用,所以使用完畢后要及時清理(尤其使用線程池時)。
49 ?寫出3條你遵循的多線程最佳實(shí)踐
多用同步類,少用wait,notify
少用鎖,應(yīng)當(dāng)縮小同步范圍
給線程一個自己的名字
多用并發(fā)集合少用同步集合
50. 為什么阿里發(fā)布的 Java開發(fā)手冊中強(qiáng)制線程池不允許使用 Executors 去創(chuàng)建?
這是因?yàn)?,JDK開發(fā)者提供了線程池的實(shí)現(xiàn)類都是有坑的,如newFixedThreadPool和newCachedThreadPool都有內(nèi)存泄漏的坑。
[1]Semaphore 使用及原理: https://zhuanlan.zhihu.com/p/98593407
[2]Java線程間通信方式講解: http://www.codebaoku.com/it-java/it-java-227064.html
編輯:黃飛
?
評論
查看更多