1. 場景困惑:
? ? 在主線程中開啟一個(gè)線程t1 , 那么我如何能夠獲取這個(gè)線程t1的執(zhí)行狀態(tài):是否開始執(zhí)行奸例?是否blocked穆趴?是否running氓奈?是否runnable?是否執(zhí)行完畢Dead葡幸?執(zhí)行結(jié)果是什么氓仲?
? ? 我以為 Future模式的能力能夠令我有辦法知道線程t1處于blocked狀態(tài)坦仍,可是我看完FutureTask的源碼后發(fā)現(xiàn)不是鳍烁。僅僅只有以下的幾種:
private static final int NEW? ? ? ? ? = 0;
private static final int COMPLETING? = 1;
private static final int NORMAL? ? ? = 2;
private static final int EXCEPTIONAL? = 3;
private static final int CANCELLED? ? = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED? = 6;
FutureTask 是Runnable和Future的實(shí)現(xiàn)類。其中Runnable的run()是啟動(dòng)線程以及用來變更線程t1的上述狀態(tài)的繁扎,F(xiàn)uture的實(shí)現(xiàn)方法則是用于管理操作線程的:如取消\中斷線程幔荒、查詢是否執(zhí)行完畢、以及存放該線程的返回值(Callable對象返回值)和狀態(tài) 梳玫。
FutureTask實(shí)現(xiàn)的Runnable的run()方法中記錄線程t1對應(yīng)狀態(tài)的邏輯機(jī)理如下:
? ? 個(gè)人感慨:這種模式的確是棒棒滴爹梁!有以下優(yōu)秀點(diǎn):
1.? 將業(yè)務(wù)邏輯 抽離出run方法、封裝到第三方的類Callable的call()中提澎,Runnale的run方法則用來變更當(dāng)前線程的執(zhí)行狀態(tài)姚垃。
? ? ? ? 2.? Future的各方法用于操縱查詢線程的執(zhí)行狀態(tài)。
3.? Runnable和Future的結(jié)合實(shí)現(xiàn)(即FutureTask類)盼忌,則剛好實(shí)現(xiàn)了對 線程的執(zhí)行的管理积糯。由于將業(yè)務(wù)邏輯封裝到第三方Callable中,那么實(shí)現(xiàn)了 線程管理與業(yè)務(wù)邏輯的解耦G础看成!即FutureTask類可以作為通用的線程狀態(tài)管理模版!
? ? ? 不過跨嘉,由于FutureTask源碼中的狀態(tài)記錄字段stat 是使用valotile修飾的 而且 線程的輸出結(jié)果outcome 也是 一個(gè)普通private變量而已川慌,所以這令我們只能 new 一個(gè)FutureTask 對應(yīng)一個(gè) 線程......否則還是會(huì)出現(xiàn) 線程安全問題.....
更高的領(lǐng)悟:對于一個(gè)原始樸素或者固定約定俗成的模式,F(xiàn)uture模式啟發(fā)了我如何將之改造成一個(gè)模版:將原來業(yè)務(wù)邏輯的嵌入點(diǎn)祠乃,改為用第三方類來封裝梦重,從而從原來的模式中抽離出來。
以上領(lǐng)悟自:http://blog.csdn.net/bboyfeiyu/article/details/24851847
2.? 一張圖理解線程的各狀態(tài)以及轉(zhuǎn)換
a. 阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán)亮瓷,暫時(shí)停止運(yùn)行琴拧。直到線程進(jìn)入Runnable狀態(tài),才有機(jī)會(huì)轉(zhuǎn)到Running狀態(tài)寺庄。阻塞的情況分三種:
(一)等待阻塞:運(yùn)行的線程執(zhí)行wait()方法艾蓝,JVM會(huì)把該線程放入等待池中力崇。(wait會(huì)釋放持有的鎖)
(二)同步阻塞:運(yùn)行的線程在獲取對象的同步鎖時(shí)斗塘,若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池中亮靴。
(三)其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法馍盟,或者發(fā)出了I/O請求時(shí),JVM會(huì)把該線程置為阻塞狀態(tài)茧吊。當(dāng)sleep()狀態(tài)超時(shí)贞岭、join()等待線程終止或者超時(shí)八毯、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入Runnable狀態(tài)瞄桨。(注意,sleep是不會(huì)釋放持有的鎖)
b.? 處于 同步阻塞 (鎖池)中的線程话速,拿到對象的鎖標(biāo)識(shí)后并不是立馬執(zhí)行的,而是進(jìn)入Runnable狀態(tài)芯侥。你會(huì)發(fā)現(xiàn)泊交,所有的阻塞結(jié)束后,都得進(jìn)入 Runnable狀態(tài)等待OS調(diào)度柱查。而不是立馬就執(zhí)行廓俭。
c.? 一個(gè)線程如果通過執(zhí)行了 o.wait()來釋放鎖后進(jìn)入等待阻塞狀態(tài)(等待池中),等候接收到o.notify()的通知唉工,此線程就會(huì)等待其他線程釋放對象鎖(cpu會(huì)將之從 等待池 轉(zhuǎn)移到 鎖池中)并且和其他同在鎖池中的線程一起競爭對象鎖研乒。終于,一陣搏殺淋硝,它獲取到鎖了雹熬,于是就進(jìn)入了Runnable狀態(tài)等等CPU的調(diào)度。
d.? 一個(gè)線程如果通過執(zhí)行了 o.wait()來釋放鎖以等待奖地,那么就會(huì)一共經(jīng)歷兩次阻塞才能去到Runnable狀態(tài):等待阻塞(等待池中)和同步阻塞(鎖池中)橄唬。如果是通過t0.join()或Thread.sleep()阻塞,則是經(jīng)歷一次阻塞就可以進(jìn)入Runnable狀態(tài)参歹。
e.? o.wait() 和 o.notify()/o.notifyAll() 是只能在synchronized 修飾范圍內(nèi)使用仰楚。也就是說能執(zhí)行o.wait() 和 o.notify()/o.notifyAll()的線程必定是當(dāng)前唯一擁有著對象鎖的線程。當(dāng)此線程執(zhí)行o.wait()時(shí)會(huì)釋放鎖并進(jìn)入等待阻塞狀態(tài)犬庇,此時(shí)正在synchronized塊外面虎視眈眈的其他線程會(huì)有其中一個(gè)幸運(yùn)地?fù)尩芥i僧界。
3.? java線程是有優(yōu)先級的,優(yōu)先級高的線程會(huì)獲取更高的優(yōu)先級臭挽。
? ? static int MAX_PRIORITY?
? ? ? ? ? ? ? 線程可以具有的最高優(yōu)先級捂襟,取值為10。?
? ? ? ? static int MIN_PRIORITY?
? ? ? ? ? ? ? 線程可以具有的最低優(yōu)先級欢峰,取值為1葬荷。?
? ? ? ? static int NORM_PRIORITY?
? ? ? ? ? ? ? 分配給線程的默認(rèn)優(yōu)先級,取值為5
? ? 其中主線程的默認(rèn)優(yōu)先級為Thread.NORM_PRIORITY纽帖。
note:
1.“線程的優(yōu)先級有繼承關(guān)系宠漩,比如線程A中創(chuàng)建了線程B , 后者會(huì)擁有與前者等同的優(yōu)先級。
2.? JVM提供了10個(gè)線程優(yōu)先級懊直,但與常見的操作系統(tǒng)都不能很好的映射扒吁。如果希望程序能移植到各個(gè)操作系統(tǒng)中,應(yīng)該僅僅使用Thread類有以下三個(gè)靜態(tài)常量作為優(yōu)先級室囊,這樣能保證同樣的優(yōu)先級采用了同樣的調(diào)度方式雕崩。
3.? 線程讓步:Thread.yield() 方法魁索,暫停當(dāng)前正在執(zhí)行的線程對象,把執(zhí)行機(jī)會(huì)讓給相同或者更高優(yōu)先級的線程盼铁。但是粗蔚,實(shí)際中無法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中饶火。
? ? 4.? join() :往往用于A線程需要B線程的處理結(jié)果 的場景支鸡。即:A線程中調(diào)用B.join(),那么A線程會(huì)等待線程B的run()執(zhí)行完畢后才進(jìn)入Runnable狀態(tài)趁窃。不過要小心的是 假如線程B中又有join\sleeping 等令自己阻塞的方法時(shí)牧挣,那么 線程A 就得等待很久了。
從JDK源碼看join():
public finalsynchronized void join(long millis) throws InterruptedException {
? ? ? ? ? ? ? ? long base = System.currentTimeMillis();
? ? ? ? ? ? ? ? long now = 0;
? ? ? ? ? ? ? ? if (millis < 0) {
? ? ? ? ? ? ? ? ? ? throw new IllegalArgumentException("timeout value is negative");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (millis == 0) {
? ? ? ? ? ? ? ? ? ? while (isAlive()) {
? ? ? ? ? ? ? ? wait(0);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? while (isAlive()) {
? ? ? ? ? ? ? ? ? ? ? ? long delay = millis - now;
? ? ? ? ? ? ? ? ? ? ? ? if (delay <= 0) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? wait(delay);
? ? ? ? ? ? ? ? ? ? ? ? now = System.currentTimeMillis() - base;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
可以看到 調(diào)用t1.join() 實(shí)際上底層是調(diào)用 t1.wait()醒陆。這里有點(diǎn)繞:我們知道 o.wait() 會(huì)通知jvm將 當(dāng)前線程放到 等待池 瀑构。同理,t1 也是一個(gè)對象芭倌Α寺晌!Main線程中調(diào)用t1.join() (獲取t1對象的對象鎖[join(**)是synchronized修飾的]),也就是說Main線程執(zhí)行了 t1這個(gè)對象的wait(),? 那么jvm也會(huì)將Main線程(釋放t1對象的鎖后[join(**)是synchronized修飾的])放到等待池中澡刹。此時(shí)Main線程得等待線程t1執(zhí)行完畢才會(huì) 獲取t1對象的鎖才進(jìn)入Runnable狀態(tài)等待機(jī)會(huì)執(zhí)行呻征。
? ? ? ? 由上述,也可以看到罢浇,其實(shí)Thread對象也跟普通對象一樣陆赋,都是堆對象,沒有其他區(qū)別嚷闭。只不過Thread對象幸運(yùn)地具備通知os創(chuàng)建新線程的能力(start())而已攒岛。
延伸猜測:Thread對象t1 對應(yīng)的 線程t 在執(zhí)行完run()后 是不是額外還"偷偷地"執(zhí)行了t1.notifyAll()? 如果不是這樣,那么為何 調(diào)用了t1.join() 的其他線程怎么可以被喚醒呢胞锰?
5.sleep()和yield()的區(qū)別
sleep()和yield()的區(qū)別):sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài)灾锯,所以執(zhí)行sleep()的線程在指定的時(shí)間內(nèi)肯定不會(huì)被執(zhí)行;yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài)嗅榕,所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行顺饮。
sleep 方法使當(dāng)前運(yùn)行中的線程睡眼一段時(shí)間,進(jìn)入不可運(yùn)行狀態(tài)凌那,這段時(shí)間的長短是由程序設(shè)定的兼雄,yield 方法使當(dāng)前線程讓出 CPU 占有權(quán),但讓出的時(shí)間是不可設(shè)定的案怯。實(shí)際上君旦,yield()方法對應(yīng)了如下操作:先檢測當(dāng)前是否有相同優(yōu)先級的線程處于同可運(yùn)行狀態(tài)澎办,如有嘲碱,則把 CPU? 的占有權(quán)交給此線程金砍,否則,繼續(xù)運(yùn)行原來的線程麦锯。所以yield()方法稱為“退讓”恕稠,它把運(yùn)行機(jī)會(huì)讓給了同等優(yōu)先級的其他線程
另外,sleep 方法允許較低優(yōu)先級的線程獲得運(yùn)行機(jī)會(huì)扶欣,但 yield()? 方法執(zhí)行時(shí)鹅巍,當(dāng)前線程仍處在Runnable狀態(tài),所以料祠,既然優(yōu)先級不同的線程都處于Runnable狀態(tài)骆捧,那么不可能讓出較低優(yōu)先級的線程些時(shí)獲得 CPU 占有權(quán)。在一個(gè)運(yùn)行系統(tǒng)中髓绽,如果較高優(yōu)先級的線程沒有調(diào)用 sleep 方法敛苇,又沒有受到 I\O 阻塞,那么顺呕,較低優(yōu)先級線程只能等待所有較高優(yōu)先級的線程運(yùn)行結(jié)束枫攀,才有機(jī)會(huì)運(yùn)行。
6.interrupt():不要以為它是中斷某個(gè)線程株茶!它只是給線程t1發(fā)送一個(gè)中斷信號来涨,如果線程t1正在阻塞狀態(tài)(如正在sleep\wait或者準(zhǔn)備sleep\wait 以及 死鎖 時(shí)),就會(huì)立馬中斷并拋出異常启盛,從而結(jié)束蹦掐。但是如果你吃掉了這個(gè)異常,那么這個(gè)線程還是不會(huì)中斷的僵闯!
public class ThreadTest {
public static void main(String[] args) throws InterruptedException{
System.out.println("Main線程開始...");
Apple apple = new Apple();
Thread t1 = new Thread(apple);
t1.start() ;
try {
Thread.sleep(3*1000);// 休眠主線程以確保線程t1進(jìn)入休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main線程 準(zhǔn)備中斷 線程t1...");
t1.interrupt() ; //線程t1正在睡眠笤闯,我們中斷它
System.out.println("Main線程結(jié)束!");
}
}
class Apple implements Runnable{
public void run() {
System.out.println("? ? 線程"+Thread.currentThread().getName()+"開始");
try {
System.out.println("? ? 線程"+Thread.currentThread().getName()+"睡眠.....");
Thread.sleep(15*1000); // 讓當(dāng)前線程睡眠久點(diǎn)棍厂!
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("? ? 線程"+Thread.currentThread().getName()+"結(jié)束");
}
}
7. 我們在和多線程打交道颗味,常見的就是保證數(shù)據(jù)共享時(shí)的線程安全問題,無非注意把握三方面:有序性牺弹,可見性浦马,原子性。少見的則是活躍性問題张漂,性能問題等晶默。
? 個(gè)人的觀點(diǎn)是:能避免數(shù)據(jù)共享則盡量避免,否則則盡量采用低成本的同步技術(shù)航攒。
8. 多線程問題磺陡,最常見的就是線程安全的問題。我們要保證線程安全,可以從下面三方面思考如何設(shè)計(jì):
? ? 1. 共享變量是無狀態(tài)對象:共享對象的屬性都是final修飾币他,或者不對外提供setter等坞靶。
2. 能否線程封閉!蝴悉!
3.上述兩種都無法采用時(shí)彰阴,那么無法避免使用同步來保證線程安全了∨墓冢可是采用哪種同步技術(shù)呢尿这?
? ? 其中,線程封閉庆杜,就是沒有共享對象, 即每個(gè)線程都new自己的對象射众。 提現(xiàn)在如下方面:
? ? ? ? 1. 棧封閉,即使用局部變量晃财。
? ? ? ? 2. 使用ThreadLocal
3.程序控制線程封閉:容易發(fā)生線程安全的環(huán)節(jié)责球,只使用一個(gè)線程處理?拓劝?雏逾??郑临?
9.我們通常將鎖分為共享鎖和排它鎖栖博,也叫做讀鎖和寫鎖。
10.保證原子性的最簡單方式是操作系統(tǒng)指令厢洞,就是說如果一次操作對應(yīng)一條操作系統(tǒng)指令仇让,這樣肯定可以能保證原子性。但是很多操作不能通過一條指令就完成躺翻。例如丧叽,對long類型的運(yùn)算,很多系統(tǒng)就需要分成多條指令分別對高位和低位進(jìn)行操作才能完成公你。
11. 可見性與jvm內(nèi)存模型的關(guān)系:
從下圖中我們可以看出踊淳,每個(gè)線程都有一個(gè)自己的工作內(nèi)存(相當(dāng)于CPU高級緩沖區(qū)【一、二陕靠、三級緩存】迂尝,這么做的目的還是在于進(jìn)一步縮小存儲(chǔ)系統(tǒng)與CPU之間速度的差異,提高性能)剪芥,對于共享變量垄开,線程每次讀取的是工作內(nèi)存中共享變量的副本,寫入的時(shí)候也直接修改工作內(nèi)存中副本的值税肪,然后在某個(gè)時(shí)間點(diǎn)上再將工作內(nèi)存與主內(nèi)存中的值進(jìn)行同步溉躲。這樣導(dǎo)致的問題是榜田,如果線程1對某個(gè)變量進(jìn)行了修改,線程2卻有可能看不到線程1對共享變量所做的修改锻梳。
12.volatile箭券,synchronized(隱式鎖), 顯式鎖,原子變量這些同步手段都可以保證可見性唱蒸。
? 可見性底層的實(shí)現(xiàn)是通過加內(nèi)存屏障實(shí)現(xiàn)的:
1. 寫變量之后加寫屏障,保證CPU寫緩沖區(qū)的值強(qiáng)制刷新回主內(nèi)存
2. 讀變量之前加讀屏障灸叼,使緩存失效神汹,從而強(qiáng)制從主內(nèi)存讀取變量最新值
寫volatile變量 = 進(jìn)入鎖
讀volatile變量 = 釋放鎖
? ? 注意:雖說 volatile 修飾的變量 可以保證 內(nèi)存可見性,可是 這個(gè)變量的私有變量引用的其他對象的私有變量引用的其他對象中的值改變了是不保證內(nèi)存可見的古今。volatile修飾的變量僅僅保證了這個(gè)變量的成員的內(nèi)存可見性屁魏。
13. 和多線程打交道久了,會(huì)發(fā)現(xiàn)其實(shí)我們一直都是和“原子性捉腥、可見性氓拼、有序性”三性打交道。初級的時(shí)候抵碟,我們保證“三性”是通過“volatile + synchronized”組合桃漾,其中volatile保證了內(nèi)存可見性和有序性,synchronized保證了原子性拟逮∏送常可是synchronized附帶的高耗能是很不劃算的,于是敦迄,人們就想著將同步下沉到更高性能的硬件層去恋追。這樣一來避開了jvm(OS)層面的高耗能線程同步,其次利用了高速的底層硬件罚屋,而這個(gè)創(chuàng)新就是CAS.于是volatile+CAS成了經(jīng)典組合苦囱。juc并發(fā)包中的工具類都是基于volatile和CAS的。
14.? 假設(shè) volatile int i 是共享變量脾猛,i= 0:
? ? ? ? i = i+1; //會(huì)出現(xiàn)線程不安全問題
? ? 于是 我們會(huì)這樣解決線程安全問題:
? ? ? ? synchronized{
? ? ? ? ? ? i = i+1;
? ? ? ? }
? ? 可是這樣卻非常耗性能撕彤,于是我們通過CAS降低耗能,同時(shí)也保證了原子性:
? ? ? ? volatile? AtomicInteger i = new AtomicInteger(0);//初始化共享變量
? ? ? ? ............
? ? ? ? ............
? ? ? ? i.getAndIncrement();//保證了原子性
15. 為何JUC中的原子數(shù)據(jù)類型可以保證“原子性”和“低耗能”猛拴?或者說喉刘,為何可以將原子性下沉到硬件層面?原因在于java中具體的CAS操作類sun.misc.Unsafe漆弄。Unsafe類提供了硬件級別的原子操作睦裳,Java無法直接訪問到操作系統(tǒng)底層(如系統(tǒng)硬件等),為此Java使用native方法來擴(kuò)展Java程序的功能撼唾。
16. 其實(shí)廉邑,借助CAS的思想,我們自己也是很容易構(gòu)建一個(gè)非阻塞鎖,如下:
public class LockUtil {
? static final AtomicInteger atomicInteger = new AtomicInteger(0);
? /**自旋CAS,只要不符合條件蛛蒙,線程則一直在循環(huán)糙箍,這樣就可以模仿出同步效果*/
? public static void atomicLock() {
? ? ? for (; ; )
? ? ? ? if (atomicInteger.compareAndSet(0, 1)) break;
? }
? /**解鎖? ? */
? public static void atomicUnlock() {
? ? ? atomicInteger.compareAndSet(1, 0);
? }
}
可是,其實(shí)這樣是極其簡單的牵祟,而且也是耗性能深夯、應(yīng)用面狹窄的:
1)atomicLock() : 在并發(fā)場景下,你會(huì)發(fā)現(xiàn)所有的線程都在這個(gè)方法內(nèi)一直循環(huán)诺苹,直到break才停止咕晋。這其實(shí)對于CPU是一個(gè)浪費(fèi)。那么收奔,我們?yōu)楹尾粐L試讓它“停下休息”一會(huì)掌呜?
2)atomicLock() : 假如我們實(shí)現(xiàn)了上述的“停下休息一會(huì)”,那個(gè)“幸運(yùn)兒”break坪哄,然后順利完成任務(wù)再atomicUnlock()解鎖质蕉。然而,“幸運(yùn)兒”解鎖完了 翩肌,可是我們?nèi)绾握业皆瓉淼恼凇靶菹ⅰ钡木€程并喚醒其中之一呢模暗?
? ? 帶著上述問題,我們就可以很容易理解:“在 java.util.concurrent.locks包中有很多Lock的實(shí)現(xiàn)類念祭,如ReentrantLock汰蓉、 ReadWriteLock(實(shí)現(xiàn)類ReentrantReadWriteLock),其實(shí)現(xiàn)都依賴 java.util.concurrent.AbstractQueuedSynchronizer類” 棒卷。AbstractQueuedSynchronizer類對象正是負(fù)責(zé)存放“正在休息的”線程們 的集合容器顾孽。當(dāng)runtime線程執(zhí)行完原子任務(wù)后,通過AbstractQueuedSynchronizer類對象將其中一個(gè)線程喚醒比规。
? ? 至于“讓線程們停下休息會(huì)”若厚,用什么方法會(huì)比較好?AbstractQueuedSynchronizer類的源碼是通過LockSupport.park() 和 LockSupport.unPark()蜒什。其本質(zhì)是:Unsafe類测秸,即直接操縱底層來實(shí)現(xiàn)。
17. synchronized和lock性能區(qū)別
synchronized是托管給JVM執(zhí)行的灾常,而lock是java寫的控制鎖的代碼霎冯。在Java1.5中,synchronize是性能低效的钞瀑。因?yàn)檫@是一個(gè)重量級操作沈撞,需要調(diào)用操作接口,導(dǎo)致有可能加鎖消耗的系統(tǒng)時(shí)間比加鎖以外的操作還多雕什。相比之下使用Java提供的Lock對象缠俺,性能更高一些显晶。但是到了Java1.6,發(fā)生了變化壹士。synchronize在語義上很清晰磷雇,可以進(jìn)行很多優(yōu)化漾岳,有適應(yīng)自旋陕贮,鎖消除,鎖粗化找默,輕量級鎖盒使,偏向鎖等等崩掘。導(dǎo)致在Java1.6上synchronize的性能并不比Lock差。官方也表示忠怖,他們也更支持synchronize呢堰,在未來的版本中還有優(yōu)化余地抄瑟。
synchronized原始采用的是CPU悲觀鎖機(jī)制凡泣,即線程獲得的是獨(dú)占鎖。獨(dú)占鎖意味著其他線程只能依靠阻塞來等待線程釋放鎖皮假。而在CPU轉(zhuǎn)換線程阻塞時(shí)會(huì)引起線程上下文切換鞋拟,當(dāng)有很多線程競爭鎖的時(shí)候,會(huì)引起CPU頻繁的上下文切換導(dǎo)致效率很低惹资。
而Lock用的是樂觀鎖方式贺纲。所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作褪测,如果因?yàn)闆_突失敗就重試猴誊,直到成功為止。樂觀鎖實(shí)現(xiàn)的機(jī)制就是CAS操作(Compare and Swap)侮措。我們可以進(jìn)一步研究ReentrantLock的源代碼懈叹,會(huì)發(fā)現(xiàn)其中比較重要的獲得鎖的一個(gè)方法是compareAndSetState。這里其實(shí)就是調(diào)用的CPU提供的特殊指令分扎。
18. 中斷機(jī)制
? ? 1) Thread對象A.interrupt():一般被Thread對象B調(diào)用澄成。
? ? 2) Thread.currentThread.isInterrupted():
3) Thread.interrupted(): 判斷當(dāng)前線程的標(biāo)識(shí)位并返回,并且清除true!!!即true會(huì)改為false,非中斷。這一定要謹(jǐn)慎使用畏吓。
舊版本的java提供的是搶占式中斷墨状,如stop()、suspend()菲饼、resume()肾砂,可是強(qiáng)制性中斷線程是很大風(fēng)險(xiǎn)的。于是后面的版本就推行協(xié)作式中斷:tA線程調(diào)用tB.interrupt()時(shí)宏悦,底層native方法僅僅是修改了tB對象的一個(gè)標(biāo)識(shí)字段為true而已通今。這就表示傳達(dá)了tB中斷信號粥谬,至于tB是否中斷,開看tB自己所處的狀態(tài)以及自己的意向辫塌。就猶如父母叮囑在外游子要呵護(hù)好身體漏策,至于游子是否呵護(hù)就看他自己了。
? ? 注意:
A.? 對于處于阻塞中的線程臼氨,被中斷掺喻,會(huì)“盡可能快”拋出InterruptedException并且將中斷標(biāo)識(shí)位置false。即被中斷線程剛調(diào)用完:Thread.sleep()储矩、threadx.join()感耙、object.wait()。因此持隧,我們就可以用下面的代碼框架來處理線程阻塞中斷:
? ? try {
? ? ? ? //wait即硼、sleep或join
? ? } catch(InterruptedException e) {//拋異常同時(shí)會(huì)將中斷標(biāo)識(shí)位置false
? ? ? ? //某些中斷處理工作:如建議將中斷狀態(tài)設(shè)置為true。
? ? }
? ? 1.所謂“盡可能快”屡拨,我猜測JVM就是在線程調(diào)度調(diào)度的間隙檢查中斷變量只酥,速度取決于JVM的實(shí)現(xiàn)和硬件的性能。? ?
? 2.處于阻塞狀態(tài)的線程 被中斷后拋出exception并置回中斷狀態(tài)為false, 有時(shí)候是不利的, 因?yàn)檫@個(gè)中斷狀態(tài)可能會(huì)作為別的線程的判斷條件, 所以穩(wěn)妥的辦法是在處理exception的地方把狀態(tài)復(fù)位:
不過如果當(dāng)前線程處于IO阻塞狀態(tài)中的話呀狼,如異步socket I/O 和 利用Selector實(shí)現(xiàn)的異步I/O裂允,是不會(huì)拋異常的,而是等IO操作結(jié)束后會(huì)有對應(yīng)的反應(yīng)行為:詳見:Java線程中斷的本質(zhì)和編程原則 - Dlite的技術(shù)筆記 - 博客頻道 - CSDN
? ? B. 如果你正在使用的是 任務(wù)與線程分離的框架哥艇,如Executer等 绝编,那么切莫輕易調(diào)用框架中線程的.interrupt(),因?yàn)檫@些框架很有可能會(huì)利用到中斷機(jī)制貌踏,如果你自己貿(mào)然直接中斷框架管理的線程十饥,那么很有可能會(huì)破壞掉框架的管理一致性。一般框架在設(shè)計(jì)時(shí)也會(huì)考慮到這點(diǎn)祖乳,所以框架也會(huì)給出對應(yīng)于原生jdk操作的api接口逗堵,我們調(diào)用它會(huì)安全。
19. 當(dāng)兩個(gè)線程競爭同一資源時(shí)凡资,如果對資源的訪問順序敏感砸捏,就稱存在競態(tài)條件。導(dǎo)致競態(tài)條件發(fā)生的代碼區(qū)稱作臨界區(qū)隙赁。在臨界區(qū)中使用適當(dāng)?shù)耐骄涂梢员苊飧倯B(tài)條件垦藏。 界區(qū)實(shí)現(xiàn)方法有兩種,一種是用synchronized伞访,一種是用Lock顯式鎖實(shí)現(xiàn)掂骏。