昨晚看到Guide哥推送的《深入淺出Java多線程》仅父,今天一鼓作氣看了9節(jié),雖然看得很疲倦,但是對多線程的基本操作和一些原理還是有不少了解笙纤;
從進(jìn)程與線程的區(qū)別開始耗溜,進(jìn)程是由操作系統(tǒng)分配內(nèi)存與其他資源(如io),線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位省容。
線程創(chuàng)建的2種方式抖拴,繼承Thread和實(shí)現(xiàn)Runnable,其中Thread使用到了裝飾者模式腥椒,裝飾Runnable對象阿宅,擴(kuò)展Runnable的功能,又使用到了策略模式笼蛛,Thread中的Runnable的各種實(shí)現(xiàn)就是一種策略洒放;
具體使用哪個可以從2個方面考慮:
1、繼承方式還是實(shí)現(xiàn)方式滨砍;
2往湿、是否需要使用Thread類的諸多方法;
這兩種線程的執(zhí)行都是沒有返回值的惨好,如果需要返回值可以使用JDK提供的Callable接口和Future接口煌茴,需要配合ExecutorService#submit(Callable c) : Future 使用随闺,返回值通過Future#get() : T 獲取日川,get()方法會阻塞當(dāng)前線程,直到得到返回結(jié)果矩乐;Future接口中定義了取消線程的cancel()方法龄句,所以如果想取消線程的話可以使用Future的實(shí)現(xiàn)類FutureTask,cancel()操作內(nèi)部實(shí)際上是執(zhí)行Thread#interrupt()方法散罕,所以調(diào)用此方法取消并不一定能取消成功分歇。
昨天總結(jié)寫的比較晚,寫到12點(diǎn)多硬是撐不住了欧漱,今天將昨天未總結(jié)完的補(bǔ)上职抡。
線程的優(yōu)先級可以調(diào)用Thread#setPriority(int i)來設(shè)置優(yōu)先級,取值范圍1~10误甚,然而并不是優(yōu)先級高的線程一定比優(yōu)先級低得先執(zhí)行缚甩,具體還是由操作系統(tǒng)決定。
線程必須存在于線程組ThreadGroup當(dāng)中窑邦,線程的優(yōu)先級不能大于所在線程組的最大優(yōu)先級擅威,如果超過將會被線程組的最大優(yōu)先級取而代之;
線程中還有一個守護(hù)線程(Deamon)冈钦,可以通過Thread#setDaemon(boolean on)來設(shè)置郊丛,只有當(dāng)所有非守護(hù)進(jìn)程結(jié)束后,守護(hù)線程才會自動結(jié)束。
接下來是線程的6種狀態(tài)以及它們之間的轉(zhuǎn)換:NEW厉熟、RUNNABLE导盅、BLOCKED、WATTING揍瑟、TIMED_WATTING认轨、TERMINATED
NEW
→ RUNNABLE
調(diào)用start(),如果有其他線程拿到鎖了月培,則進(jìn)入BLOCKED狀態(tài)嘁字;
RUNNABLE
→ BLOCKED
等待鎖
RUNNABLE
→ WAITING
調(diào)用當(dāng)前鎖調(diào)用wait()、其他線程調(diào)用join()
RUNNABLE
→ TIMED_WAITING
調(diào)用wait(time)杉畜、join(time)纪蜒、sleep(time)
WAITING/TIMED_WAITING
→ RUNNABLE
調(diào)用Object.notify()、notifyAll()此叠、LockSupport.unpark;
需要注意的是線程調(diào)用start()方法必定會進(jìn)入RUNNABLE
狀態(tài)纯续,遇到鎖后才有可能進(jìn)入BLOCKED
狀態(tài);
接下來是線程間的通信灭袁,java線程機(jī)制主要采用鎖+同步的方式來進(jìn)行線程間的通信
Synchronized:同步標(biāo)識猬错,需要一個對象做為鎖,且只能是Class對象或者Object對象茸歧;如果是靜態(tài)方法中使用這個默認(rèn)為該類.class倦炒,如果是普通方法標(biāo)記則默認(rèn)為this,同步代碼塊需要通過參數(shù)傳入鎖软瞎,如果兩段同步的鎖相同逢唤,那么線程可以從第一段同步執(zhí)行完后,直接進(jìn)入第二段同步代碼涤浇,不需要競爭鎖鳖藕,因?yàn)殒i只能被一個對象持有;
join():當(dāng)一個線程調(diào)用join后只锭,那么其他線程必須進(jìn)入WAITING等待這個線程執(zhí)行完畢后才會執(zhí)行著恩,主線程也不例外;
Object#wait()與Thread#Sleep(long t)區(qū)別:
1蜻展、wait()會釋放鎖喉誊,而sleep()不會;
2铺呵、wait()只有在拿到鎖的情況才能執(zhí)行裹驰,而sleep()在線程中隨時可以執(zhí)行;
接著作者提到了ThreadLocal類主要是為每個線程創(chuàng)建一個副本片挂,用來存儲線程自己的私有變量幻林;
呼啦啦贞盯,這才總結(jié)了一半,不總結(jié)還真不知道昨天學(xué)了多少知識沪饺!總結(jié)寫的好心累啊躏敢,一方面想著學(xué)習(xí)進(jìn)度,一方面手頭還有些工作要弄整葡,不管了件余,加油吧!少年遭居!
下面是原理篇啼器,主要是java內(nèi)存模型、volatile關(guān)鍵字俱萍、鎖的幾種類別端壳,先從java內(nèi)存模型講起吧,
java運(yùn)行時內(nèi)存主要由方法區(qū)枪蘑、堆损谦、虛擬機(jī)棧、本地方法棧岳颇、程序計(jì)數(shù)器
這無大部分組成照捡,對于每個線程來說,堆是共有的话侧,而棧是私有的栗精,每個線程都含有一個本地內(nèi)存,保存著該線程使用的堆空間中共享變量的副本掂摔,線程只與本地內(nèi)存交互术羔,而本地內(nèi)存什么時候更新到共享內(nèi)存由虛擬機(jī)控制赢赊。線程間的通信必須通過共享內(nèi)存來進(jìn)行通信乙漓。
講到共享內(nèi)存通信,那不得不提volatile關(guān)鍵字释移,其作用主要有2個:
1叭披、內(nèi)存共享:當(dāng)一個線程對volatile修飾的變量進(jìn)行寫操作時,java運(yùn)行模型會立即把該線程對應(yīng)的本地內(nèi)存中的值刷新到主內(nèi)存中玩讳,當(dāng)一個線程對volatile修飾的變量進(jìn)行讀操作時涩蜘,java運(yùn)行模型會將本地內(nèi)存中的值設(shè)置成無效,并從主內(nèi)存中讀妊俊同诫;
2、防止指令重排樟澜;
講到volatile關(guān)鍵字不得不提單例模式雙重檢查+鎖模式误窖,其中單例變量就使用了volatile關(guān)鍵字標(biāo)記叮盘,而它的作用主要就是防止指令重排,因?yàn)閚ew Object()操作在編譯器內(nèi)有3條指令:分配內(nèi)存霹俺、初始化柔吼、賦值給變量
,而如果還沒有實(shí)例化就復(fù)制給了變量丙唧,這是另一個線程剛好進(jìn)入到第一層檢查愈魏,那么就會直接返回一個未初始化完成的對象;
最后想际,了解到了同步鎖還存在升級機(jī)制培漏,而鎖的狀態(tài)也分為4種:無鎖狀態(tài)、偏向鎖胡本、輕量級鎖北苟、重量級鎖
既然講到鎖,那得弄清楚鎖存在哪個地方打瘪,鎖都是基于對象的友鼻,鎖信息存放在java對象頭中,
java對象頭占用2個字寬闺骚,如果是數(shù)組則占用3個字寬彩扔,多一個字寬用來存儲數(shù)組的長度,一個字寬對應(yīng)操作系統(tǒng)的位數(shù)僻爽,如果是64位操作系統(tǒng)中虫碉,那java對象頭就占了128位,這128位胸梆,主要存儲2種數(shù)據(jù)敦捧,hashCode+鎖信息、類型數(shù)據(jù)指針碰镜;
對象頭中就保存著鎖的狀態(tài)兢卵,線程在第一次進(jìn)入同步代碼塊時,會將線程ID保存到鎖的對象頭中绪颖,并且把鎖設(shè)置為偏向鎖的信息也保存進(jìn)去秽荤;
線程再次進(jìn)入同步代碼塊中時,會去檢查鎖對象頭里的Mark Word信息柠横,如果鎖的對象頭中Mark Word里已經(jīng)有線程ID了窃款,且線程ID等于自己時,那說明該線程已經(jīng)獲得了鎖牍氛,并且不需要花費(fèi)CAS(compare and swap)操作來進(jìn)行加鎖和解鎖晨继,那么這個鎖還是偏向鎖;
如果線程ID不等于自己的線程ID搬俊,那說明有另一個線程來競爭這把鎖了紊扬,這個時候會嘗試CAS操作來替換Mark Word里的線程ID和鎖類型曲饱,CAS操作返回成功和失敗,如果成功珠月,則表示之前線程已經(jīng)不存在了扩淀,那么替換ID,不升級鎖啤挎;如果失敗驻谆,說明之前的線程還存在,這時需要暫停之前的線程庆聘,將鎖升級為輕量級鎖(這個過程有一定開銷)胜臊;
還是剛剛那個線程,在講偏向鎖升級為輕量級鎖后伙判,會自旋競爭鎖象对,如果超過一定時間獲取不到,就會進(jìn)入阻塞狀態(tài)宴抚,CAS會將鎖升級為重量級鎖勒魔,未競爭到鎖的線程將會進(jìn)入阻塞狀態(tài),阻塞狀態(tài)不消耗CPU資源菇曲,但是線程間狀態(tài)的轉(zhuǎn)換需要相對較長的較長的時間冠绢,所以重量級鎖效率較低
呼!終于完了常潮,最后再來總結(jié)下這三種鎖的區(qū)別和使用場景吧弟胀。
1、偏向鎖:加鎖和解鎖操作不需要額外的消耗喊式,速度接近非同步方法孵户,但是線程競爭鎖導(dǎo)致鎖升級會造成額外的消耗,適用于多線程中同一時間段內(nèi)只有一個線程訪問同步代碼塊的情況岔留;
2夏哭、輕量級鎖:競爭鎖不會阻塞,提高了程序的響應(yīng)速度贸诚,但是始終得不到鎖的線程會自旋消耗CPU資源方庭,適用于追求響應(yīng)速度的情況;
3酱固、重量級鎖:線程競爭不使用自旋,不會消耗CPU头朱,但是線程阻塞运悲,響應(yīng)時間緩慢,同步塊執(zhí)行速度較長项钮,適用于追求吞吐量的情況班眯。