線程可以擁有自己的操作棧、程序計數(shù)器、局部變量表等資源,它與同一進程內(nèi)的其他線程共享該進程的所有資源。線程在生命周期內(nèi)存在多種狀態(tài)。有NEW(新建狀態(tài))、RUNNABLE(就緒狀態(tài))、RUNNING(運行狀態(tài))、BLOCKED(阻塞狀態(tài))、DEAD(終止狀態(tài))五中狀態(tài)。

(1) New即新建狀態(tài)
是線程被創(chuàng)建且未啟動的狀態(tài)。創(chuàng)建線程的方式有三種:第一種是繼承Thread類,第二種是實現(xiàn)Runnable接口,第三種是實現(xiàn)Callable接口。相比第一種,推薦第二種方式,因為繼承自Thread類往往不符合里氏代換原則,而實現(xiàn)Runnable接口可以使編程更加靈活,對外暴露的細節(jié)比較少,讓使用者專注于實現(xiàn)線程的run()方法上。第三種call()聲明如下:

由此可知,Callable與Runnable有兩點不同:第一,可以通過call()獲得返回值。前兩種方式都有一個共同的缺陷,即在任務(wù)執(zhí)行完成后,無法直接獲取執(zhí)行結(jié)果,需要借助共享變量等獲取,而Callable和Future則很好地解決了這個問題;第二,call()可以拋出異常。而Runnable只有通過setDefaultUncaughtExceptionHandler()的方式才能在主線程中捕捉到子線程異常。
(2)RUNNABLE,即就緒狀態(tài)
是調(diào)用start()之后運行之前的狀態(tài)。線程的start()不能被多次調(diào)用,否則會拋出IllegalStateException異常。
(3)RUNNING,即運行狀態(tài)
是run()正在執(zhí)行時線程的狀態(tài)。線程可能會由于某些因素而退出RUNNING,如時間、異常、鎖、調(diào)度等。
(4)BLOCKED,即阻塞狀態(tài)
進入此狀態(tài),有以下情況。
同步阻塞:鎖被其他線程占用
主動阻塞:調(diào)用Thread()的某些方法,主動讓出CPU執(zhí)行權(quán),比如sleep()、join()等。
等待阻塞:執(zhí)行了wait()。
(5)DEAD,即終止狀態(tài)
是run()執(zhí)行結(jié)束,或因異常退出后的狀態(tài),此狀態(tài)不能逆轉(zhuǎn)。
(順手插一個自己的記錄:start()和run()方法的區(qū)別:
1、start方法用來啟動相應(yīng)的線程;
2、run方法只是thread的一個普通方法,在主線程里執(zhí)行;
3、需要并行處理的代碼放在run方法中,start方法啟動線程后自動調(diào)用run方法;
4、run方法必去是public的訪問權(quán)限,返回類型為void。)
線程安全問題只在多線程環(huán)境下才出現(xiàn),單線程串行執(zhí)行不存在此問題。保證并發(fā)場景下的線程安全,可以從以下四個維度考量:
(1)數(shù)據(jù)單線程可見。
單線程總是安全的。通過限制數(shù)據(jù)僅在單線程內(nèi)可見,可以避免數(shù)據(jù)被其他線程篡改。最典型的就是線程局部變量,它存儲在獨立虛擬機棧幀變量表中,與其他線程毫無瓜葛。ThreadLocal就是采用這種方式來實現(xiàn)線程安全的。
(2)只讀對象。
只讀對象總是安全的。他的特性是允許復(fù)制、拒絕寫入。最典型的只讀對象有String、Integer等。一個對象想要拒絕任何寫入,必須要滿足以下條件:使用final關(guān)鍵字修飾類,避免被繼承;使用private final關(guān)鍵字避免屬性被中途修改;沒有任何更新方法;返回值不能可變對象為引用。
(3)線程安全類。
某些線程安全類的內(nèi)部有非常明確的線程安全機制。比如Stringbuffer就是一個線程安全類,它采用synchronized關(guān)鍵字來修飾相關(guān)方法。
(4)同步與鎖機制。
如果想要對某個對象進行并發(fā)更新操作,但又不屬于上述三類,需要開發(fā)工程師在代碼中實現(xiàn)安全的同步機制。雖然這個機制支持的并發(fā)場景很有價值,但非常復(fù)雜且容易出現(xiàn)問題。
線程的安全核心理念就是“要么只讀,要么加鎖”。合理利用好JDK提供的并發(fā)包,往往能化腐朽為神奇。Java并發(fā)包(java.util.concurrent, JUC)中大多數(shù)類注釋都寫有:@author Doug Lea。如果說Java是一本史書,那么Doug Lea絕對是開疆拓土的偉大人物。Doug Lea在大學(xué)當(dāng)老師時,專攻并發(fā)編程和并發(fā)數(shù)據(jù)結(jié)構(gòu)設(shè)計,主導(dǎo)設(shè)計了JUC并發(fā)包,提高了Java并發(fā)編程的易用性,大大推進了Java的商用進程。并發(fā)包主要分成以下幾個類族:
(1)線程同步類。
這些類使線程間的協(xié)調(diào)更加容易,支持了更加豐富的線程協(xié)調(diào)場景,逐步淘汰了使用Object的wait()和notify()進行同步的方式。主要代表為CountDownLatch、Semaphore、CyclicBarrier等。
(2)并發(fā)集合類。
集合并發(fā)操作的要求是執(zhí)行速度快,提取數(shù)據(jù)準。最著名的類非ConcurrentHas和Map莫屬,它不斷地優(yōu)化,由剛開始的鎖分段到后來的CAS,不斷地提升并發(fā)性能。其他還有ConcurrentSkipListMap、CopyOnWriterArrayList、BlockingQueue等。
(3)線程管理類。
雖然Thread和ThreadLocal在JDK1.0就已經(jīng)引入,但是真正把Thread發(fā)揚光大的是線程池。根據(jù)實際場景的需要,提供了多重創(chuàng)建線程池的快捷方式,如使用Executors靜態(tài)工廠或者使用ThreadPoolExecutor等。另外,通過ScheduledExectorService來執(zhí)行定時任務(wù)。
(4)鎖相關(guān)類。
鎖以Lock接口為核心,派生出一些在實際場景中進行互在斥操作的鎖相關(guān)類。最有名的是ReentrantLock。鎖的很多概念在弱化,是因為鎖的實現(xiàn)在各種場景中已經(jīng)通過類庫封裝進去了。
并發(fā)包中的類族有很多,差異比較微妙,開發(fā)工程師需要有很好的Java基礎(chǔ)、邏輯思維能力,還需要有一定的數(shù)據(jù)結(jié)構(gòu)基礎(chǔ),才能夠徹底分清各個類族的優(yōu)點、缺點及差異點。
解決線程安全問題的能力時開發(fā)工程師進階的重要能力之一。由于初創(chuàng)公司的業(yè)務(wù)流量通常比較小,再加上其初級程序員缺乏線程安全意識。所以,即使出現(xiàn)了由高并發(fā)導(dǎo)致的錯誤,往往也由于復(fù)現(xiàn)難度大、追蹤困難而不了了之。但是在后期的系統(tǒng)重構(gòu)中,這些公司一定會為以上線程安全隱患買單。