1、線程安全性
定義:多個線程之間的操做不管採用何種執行時序或交替方式,都要保證不變性條件不被破壞
「共享」:變量能夠由多個線程同時訪問;
「可變」:變量的值在其生命週期內能夠發生改變
若是當多個線程訪問同一個可變的狀態變量時,沒有使用合適的同步,那麼程序將會出現錯誤。有三種方式能夠修復該問題:
競態條件:
最多見的類型就是「先檢查後執行」操做,即經過一個可能失效的觀測接口來決定下一步的動做。
2、線程啓動
- 繼承Thread類,重寫裏面的run方法,用start方法啓動線程
- 實現Runnable接口,實現裏面的run方法,用new Thread(Runnable target).start()方法來啓動
start()和run()
線程的啓動並非簡單的調用了run方法,而是由一個線程調度器來分別調用全部線程的run方法,普通的run方法若是沒有執行完是不會返回的,也就是會一直執行下去,這樣run方法下面的方法就不可能會執行了,但是線程裏的run方法卻不同,它只有必定的CPU時間,執行事後就給別的線程了,這樣反覆的把CPU的時間切來切去,由於切換的速度很快,因此咱們就感受是不少線程在同時運行同樣
(01) mythread.run()是在「主線程main」中調用的,該run()方法直接運行在「主線程main」上。
(02) mythread.start()會啓動「線程mythread」,「線程mythread」啓動以後,會調用run()方法;此時的run()方法是運行在「線程mythread」上。
3、線程同步
內置鎖(互斥鎖):
在java中,每個對象有且僅有一個同步鎖。這也意味着,同步鎖是依賴於對象而存在。線程在進入同步代碼塊以前會自動得到鎖,而且在退出同步代碼塊時自動釋放鎖。同一時間最多隻有一個線程持有該鎖,其餘線程必須等待或者阻塞,直到該線程釋放鎖。
若是持有鎖的時間過長,將會帶來性能問題。
實例鎖 -- 鎖在某一個實例對象上。若是該類是單例,那麼該鎖也具備全局鎖的概念。
實例鎖對應的就是synchronized關鍵字。
全局鎖 -- 該鎖針對的是類,不管實例多少個對象,那麼線程都共享該鎖。
全局鎖對應的就是static synchronized(或者是鎖在該類的class或者classloader對象上)。
volatile至關於synchronized的弱實現,也就是說volatile實現了相似synchronized的語義,卻又沒有鎖機制。它確保對volatile字段的更新以可預見的方式告知其餘的線程。
volatile包含如下語義:
(1)Java 存儲模型不會對valatile指令的操做進行重排序:這個保證對volatile變量的操做時按照指令的出現順序執行的。
(2)volatile變量不會被緩存在寄存器中(只有擁有線程可見)或者其餘對CPU不可見的地方,每次老是從主存中讀取volatile變量的結果。也就是說對於volatile變量的修改,其它線程老是可見的,而且不是使用本身線程棧內部的變量。也就是在happens-before法則中,對一個valatile變量的寫操做後,其後的任何讀操做理解可見此寫操做的結果。
儘管volatile變量的特性不錯,可是volatile並不能保證線程安全的,也就是說volatile字段的操做不是原子性的,volatile變量只能保證可見性(一個線程修改後其它線程可以理解看到此變化後的結果),要想保證原子性,目前爲止只能加鎖!
4、線程協做
線程等待:
等待的緣由多是以下幾種狀況:
(1)sleep() 的做用是讓當前線程休眠,即當前線程會從「運行狀態」進入到「休眠(阻塞)狀態」。sleep()會指定休眠時間,線程休眠的時間會大於/等於該休眠時間;在線程從新被喚醒時,它會由「阻塞狀態」變成「就緒狀態」,從而等待cpu的調度執行。
(2)經過調用join()方法使線程掛起,使本身等待另外一個線程的結果,直到另外一個線程執行完畢爲止。
(3)經過調用wait()方法使線程掛起,直到線程獲得了notify()和notifyAll()消息,線程纔會進入「可執行」狀態。
(4)yield()的做用是讓步。它能讓當前線程由「運行狀態」進入到「就緒狀態」,從而讓其它具備相同優先級的等待線程獲取執行權;可是,並不能保證在當前線程調用yield()以後,其它具備相同優先級的線程就必定能得到執行權;也有多是當前線程又進入到「運行狀態」繼續運行!
注意:
(01) wait()是讓線程由「運行狀態」進入到「等待(阻塞)狀態」,而yield()是讓線程由「運行狀態」進入到「就緒狀態」。
(02) wait()是會讓線程釋放它所持有對象的同步鎖,而yield()方法不會釋放鎖。
(03) sleep()
使當前線程暫停執行一段時間,從而讓其餘線程有機會繼續執行,但它並不釋放對象鎖
wait():
「當前線程」在調用wait()時,必須擁有該對象的同步鎖。該線程調用wait()以後,會釋放該鎖;而後一直等待直到「其它線程」調用對象的同步鎖的notify()或notifyAll()方法。而後,該線程繼續等待直到它從新獲取「該對象的同步鎖」,而後就能夠接着運行wait()後面的代碼。wait()的做用是讓「當前線程」等待,而「當前線程」是指正在cpu上運行的線程!所以調用wait()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)
wait的東西必定要notify嗎?不必定,
notify():
notify() 執行該方法的線程喚醒在對象的等待池中等待的一個線程,JVM從對象的等待池中隨機選擇一個線程,把它轉到對象的鎖池中。使線程由阻塞隊列進入就緒狀態。
notifyAll():
當前的線程已經放棄對資源的佔有,通知全部的等待線程從wait()方法後的語句開始運行。
這裏要注意一點:notify()和notifyAll()方法只是喚醒等待該對象的鎖的線程,並不決定哪一個線程可以獲取到鎖.
舉個簡單的例子:
假若有三個線程Thread一、Thread2和Thread3都在等待對象objectA的鎖,此時Thread4擁有對象objectA的鎖,當在Thread4中調用objectA.notify()方法以後,Thread一、Thread2和Thread3只有一個能被喚醒。注意,被喚醒不等於馬上就獲取了objectA的鎖,倘若在Thread4中調用objectA.notifyAll()方法,則Thread一、Thread2和Thread3三個線程都會被喚醒,至於哪一個線程接下來可以獲取到objectA的鎖就具體依賴於操做系統的調度了。
上面尤爲要注意一點,一個線程被喚醒不表明當即獲取了對象的鎖,只有等調用完notify()或者notifyAll()並退出synchronized塊,釋放對象鎖後,其他線程纔可得到鎖執行。
首先,wait()和notify(),notifyAll()是Object類的方法,sleep()和yield()是Thread類的方法。
固然因爲Thread類繼承了Object類,因此Thread也能夠調用前面三個方法
因爲每一個對象都擁有鎖,因此讓當前線程等待某個對象的鎖,固然應該經過這個對象來操做了。而不是用當前線程來操做,由於當前線程可能會等待多個對象的鎖,若是經過線程來操做,就很是複雜了。
(1).經常使用的wait方法有wait()和wait(long timeout):
void wait() 在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法前,致使當前線程等待。
void wait(long timeout) 在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量前,致使當前線程等待。
wait()後,線程會釋放掉它所佔有的「鎖標誌」,從而使線程所在對象中的其它synchronized數據可被別的線程使用。
wait()和notify()由於會對對象的「鎖標誌」進行操做,因此它們必須在synchronized函數或synchronized block中進行調用。若是在non-synchronized函數或non-synchronized block中進行調用,雖然能編譯經過,但在運 行時會發生IllegalMonitorStateException的異常。
(2).Thread.sleep(long millis),必須帶有一個時間參數。
sleep(long)使當前線程進入停滯狀態,因此執行sleep()的線程在指定的時間內確定不會被執行;
sleep(long)可以使優先級低的線程獲得執行的機會,固然也可讓同優先級和高優先級的線程有執行的機會;
sleep(long)是不會釋放鎖標誌的。
(3).yield()沒有參數。
sleep 方法使當前運行中的線程睡眼一段時間,進入不可運行狀態,這段時間的長短是由程序設定的,yield 方法使當前線程讓出CPU佔有權,但讓出的時間是不可設定的。
yield()也不會釋放鎖標誌。
實際上,yield()方法對應了以下操做: 先檢測當前是否有相同優先級的線程處於同可運行狀態,若有,則把 CPU 的佔有權交給此線程,不然繼續運行原來的線程。因此yield()方法稱爲「退讓」,它把運行機會讓給了同等優先級的其餘線程。
sleep方法容許較低優先級的線程得到運行機會,但yield()方法執行時,當前線程仍處在可運行狀態,因此不可能讓出較低優先級的線程些時得到CPU佔有權。 在一個運行系統中,若是較高優先級的線程沒有調用 sleep 方法,又沒有受到 I/O阻塞,那麼較低優先級線程只能等待全部較高優先級的線程運行結束,纔有機會運行。
yield()只是使當前線程從新回到可執行狀態,因此執行yield()的線程有可能在進入到可執行狀態後立刻又被執行。因此yield()只能使同優先級的線程有執行的機會。
sleep和yield區別:
一、sleep()方法會給其餘線程運行的機會,而不考慮其餘線程的優先級,所以會給較低線程一個運行的機會;yield()方法只會給相同優先級或者更高優先級的線程一個運行的機會。
二、當線程執行了sleep(long millis)方法後,將轉到阻塞狀態,參數millis指定睡眠時間;當線程執行了yield()方法後,將轉到就緒狀態。
三、sleep()方法聲明拋出InterruptedException異常,而yield()方法沒有聲明拋出任何異常
爲何notify(), wait()等函數定義在Object中,而不是Thread中?
Object中的wait(), notify()等函數,和synchronized同樣,會對「對象的同步鎖」進行操做。
wait()會使「當前線程」等待,由於線程進入等待狀態,因此線程應該釋放它鎖持有的「同步鎖」,不然其它線程獲取不到該「同步鎖」而沒法運行!那麼:notify()是依據什麼喚醒等待線程的?或者說,wait()等待線程和notify()之間是經過什麼關聯起來的?答案是:依據「對象的同步鎖」。
負責喚醒等待線程的那個線程(咱們稱爲「喚醒線程」),它只有在獲取「該對象的同步鎖」(這裏的同步鎖必須和等待線程的同步鎖是同一個),而且調用notify()或notifyAll()方法以後,才能喚醒等待線程。雖然,等待線程被喚醒;可是,它不能馬上執行,由於喚醒線程還持有「該對象的同步鎖」。必須等到喚醒線程釋放了「對象的同步鎖」以後,等待線程才能獲取到「對象的同步鎖」進而繼續運行。
總之,notify(), wait()依賴於「同步鎖」,而「同步鎖」是對象鎖持有,而且每一個對象有且僅有一個!這就是爲何notify(), wait()等函數定義在Object類,而不是Thread類中的緣由。
Condition
Condition是在java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協做,相比使用Object的wait()、notify(),使用Condition的await()、signal()這種方式實現線程間協做更加安全和高效。所以一般來講比較推薦使用Condition,Java阻塞隊列其實是使用了Condition來模擬線程間協做。
- Condition是個接口,基本的方法就是await()和signal()方法;
- Condition依賴於Lock接口,生成一個Condition的基本代碼是lock.newCondition()
- 調用Condition的await()和signal()方法,都必須在lock保護以內,就是說必須在lock.lock()和lock.unlock之間纔可使用
Conditon中的await()對應Object的wait();
Condition中的signal()對應Object的notify();
Condition中的signalAll()對應Object的notifyAll()。
5、線程中斷
Java的線程調度不提供搶佔式中斷,而採用協做式的中斷。其實,協做式的中斷,原理很簡單,就是輪詢某個表示中斷的標記。
一般,咱們經過「標記」方式終止處於「運行狀態」的線程。其中,包括「中斷標記」和「額外添加標記」。
(01) 經過「中斷標記」終止線程。
形式以下:
@Override
publicvoid run() {
while (!isInterrupted()) {
// 執行任務... }
}
說明:isInterrupted()是判斷線程的中斷標記是否是爲true。當線程處於運行狀態,而且咱們須要終止它時;能夠調用線程的interrupt()方法,使用線程的中斷標記爲true,即isInterrupted()會返回true。此時,就會退出while循環。
注意:interrupt()並不會終止處於「運行狀態」的線程!它會將線程的中斷標記設爲true。
(02) 經過「額外添加標記」。
形式以下:
private volatile boolean flag = true;
protected void stopTask() {
flag = false;
}
@Override
public void run() {
while (flag) {
// 執行任務... }
}
說明:線程中有一個flag標記,它的默認值是true;而且咱們提供stopTask()來設置flag標記。當咱們須要終止該線程時,調用該線程的stopTask()方法就可讓線程退出while循環。
注意:將flag定義爲volatile類型,是爲了保證flag的可見性。即其它線程經過stopTask()修改了flag以後,本線程能看到修改後的flag的值。
一般,咱們經過「中斷」方式終止處於「阻塞狀態」的線程。
當線程因爲被調用了sleep(), wait(), join()等方法而進入阻塞狀態;若此時調用線程的interrupt()將線程的中斷標記設爲true。因爲處於阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException異常。將InterruptedException放在適當的爲止就能終止線程
@Override
public void run() {
try {
while (true) {
// 執行任務... }
} catch (InterruptedException ie) {
// 因爲產生InterruptedException異常,退出while(true)循環,線程終止! }
}
注意:對InterruptedException的捕獲務通常放在while(true)循環體的外面,這樣,在產生異常時就退出了while(true)循環。
參考資料:
梁飛:Java併發編程常識.pptx