學習筆記1:java多線程機制之線程概念

因爲種種需求,今天開始由淺入深的學習java的多線程機制,而java語言的一大特性點就是內置對多線程的支持。java


如下內容出自: http://blog.csdn.net/jiangwei0910410003/article/details/19962627  : 緩存

線程的生命週期:

1.新建狀態(New):用new語句建立的線程對象處於新建狀態,此時它和其它的java對象同樣,僅僅在堆中被分配了內存 
2.就緒狀態(Runnable):當一個線程建立了之後,其餘的線程調用了它的start()方法,該線程就進入了就緒狀態。處於這個狀態的 線程位於可運行池中,等待得到CPU的使用權 
3.運行狀態(Running): 處於這個狀態的線程佔用CPU,執行程序的代碼 
4.阻塞狀態(Blocked): 當線程處於阻塞狀態時,java虛擬機不會給線程分配CPU,直到線程從新進入就緒狀態,它纔有機會轉到 運行狀態。 
阻塞狀態分爲三種狀況: 
1)、 位於對象等待池中的阻塞狀態:當線程運行時,若是執行了某個對象的wait()方法,java虛擬機就回把線程放到這個對象的等待池中 
2)、 位於對象鎖中的阻塞狀態,當線程處於運行狀態時,試圖得到某個對象的同步鎖時,若是該對象的同步鎖已經被其餘的線程佔用,JVM就會把這個線程放到這個對象的瑣池中。 
安全

3)、 其它的阻塞狀態:當前線程執行了sleep()方法,或者調用了其它線程的join()方法,或者發出了I/O請求時,就會進入這個狀態中。多線程


1、建立並運行線程

當調用start方法後,線程開始執行run方法中的代碼。線程進入運行狀態。能夠經過Thread類的isAlive方法來判斷線程是否處於運行狀態。當線程處於運行狀態時,isAlive返回true,當isAlive返回false時,可能線程處於等待狀態,也可能處於中止狀態。app


2、掛起和喚醒線程

一但線程開始執行run方法,就會一直到這個run方法執行完成這個線程才退出。但在線程執行的過程當中,能夠經過兩個方法使線程暫時中止執行。這兩個方法是suspend和sleep。在使用suspend掛起線程後,能夠經過resume方法喚醒線程。而使用sleep使線程休眠後,只能在設定的時間後使線程處於就緒狀態(在線程休眠結束後,線程不必定會立刻執行,只是進入了就緒狀態,等待着系統進行調度)。suspend方法是不釋放鎖
異步

雖然suspend和resume能夠很方便地使線程掛起和喚醒,但因爲使用這兩個方法可能會形成一些不可預料的事情發生,所以,這兩個方法被標識爲deprecated(棄用)標記,這代表在之後的jdk版本中這兩個方法可能被刪除,因此儘可能不要使用這兩個方法來操做線程。函數


3、終止線程的三種方法

有三種方法可使終止線程。
1.  使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。
2.  使用stop方法強行終止線程(線程中調用了阻塞代碼)(這個方法不推薦使用,由於stop是依靠拋出異常來結束線程的,也可能發生不可預料的結果)。若是沒有調用阻塞代碼,能夠正常結束線程。
學習

3.  使用interrupt方法中斷線程(線程中調用了阻塞代碼)(其實這種方法也是經過拋出異常來結束線程的)。若是沒有調用阻塞代碼,能夠經過判斷線程的中斷標誌位來介紹線程。this


線程的幾個方法:

join():等待此線程死亡後再繼續,可以使異步線程變爲同步線程,join方法是不會釋放鎖
interrupt():中斷線程,被中斷線程會拋InterruptedException
wait():等待獲取鎖:表示等待獲取某個鎖執行了該方法的線程釋放對象的鎖,JVM會把該線程放到對象的等待池中。該線程等待其它線程喚醒 
notify():執行該方法的線程喚醒在對象的等待池中等待的一個線程,JVM從對象的等待池中隨機選擇一個線程,把它轉到對象的鎖池中。使線程由阻塞隊列進入就緒狀態
sleep():讓當前正在執行的線程休眠,有一個用法能夠代替yield函數——sleep(0)
yield():暫停當前正在執行的線程對象,並執行其餘線程。也就是交出CPU一段時間(其餘一樣的優先級或者更高優先級的線程能夠獲取到運行的機會)
sleep和yield區別:
一、sleep()方法會給其餘線程運行的機會,而不考慮其餘線程的優先級,所以會給較低線程一個運行的機會;yield()方法只會給相同優先級或者更高優先級的線程一個運行的機會。 
二、當線程執行了sleep(long millis)方法後,將轉到阻塞狀態,參數millis指定睡眠時間;當線程執行了yield()方法後,將轉到就緒狀態。 
三、sleep()方法聲明拋出InterruptedException異常,而yield()方法沒有聲明拋出任何異常 
spa

四、sleep()方法比yield()方法具備更好的移植性 


若是但願明確地讓一個線程給另一個線程運行的機會,能夠採起如下的辦法之一:
一、調整各個線程的優先級 
二、讓處於運行狀態的線程調用Thread.sleep()方法 
三、讓處於運行狀態的線程調用Thread.yield()方法 

四、讓處於運行狀態的線程調用另外一個線程的join()方法


首先,wait()和notify(),notifyAll()是Object類的方法,sleep()和yield()是Thread類的方法。
(1).經常使用的wait方法有wait()和wait(long timeout):
void wait() 在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法前,致使當前線程等待。 
void wait(long timeout) 在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量前,致使當前線程等待。
wait()後,線程會釋放掉它所佔有的「鎖標誌」,從而使線程所在對象中的其它synchronized數據可被別的線程使用。

wait()和notify()由於會對對象的「鎖標誌」進行操做,因此它們必須在synchronized函數或synchronized代碼塊中進行調用。若是在non-    synchronized函數或non-synchronized代碼塊中進行調用,雖然能編譯經過,但在運 行時會發生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()只能使同優先級的線程有執行的機會。


volitile 語義

volatile至關於synchronized的弱實現,也就是說volatile實現了相似synchronized的語義,卻又沒有鎖機制。它確保對volatile字段的更新以可預見的方式告知其餘的線程。
volatile包含如下語義:
(1)Java 存儲模型不會對valatile指令的操做進行重排序:這個保證對volatile變量的操做時按照指令的出現順序執行的。
(2)volatile變量不會被緩存在寄存器中(只有擁有線程可見)或者其餘對CPU不可見的地方,每次老是從主存中讀取volatile變量的結果。也就是說對於volatile變量的修改,其它線程老是可見的,而且不是使用本身線程棧內部的變量。也就是在happens-before法則中,對一個valatile變量的寫操做後,其後的任何讀操做理解可見此寫操做的結果。
儘管volatile變量的特性不錯,可是volatile並不能保證線程安全的,也就是說volatile字段的操做不是原子性的,volatile變量只能保證可見性(一個線程修改後其它線程可以理解看到此變化後的結果),要想保證原子性,目前爲止只能加鎖!


數據同步:

線程同步的特徵: 
一、若是一個同步代碼塊和非同步代碼塊同時操做共享資源,仍然會形成對共享資源的競爭。由於當一個線程執行一個對象的同步代碼塊時,其餘的線程仍然能夠執行對象的非同步代碼塊。(所謂的線程之間保持同步,是指不一樣的線程在執行同一個對象的同步代碼塊時,由於要得到對象的同步鎖而互相牽制) 
二、每一個對象都有惟一的同步鎖 
三、在靜態方法前面可使用synchronized修飾符,可是要注意的是鎖對象是類(用Object.class而不能用this),而不是這個類的對象。 
四、當一個線程開始執行同步代碼塊時,並不意味着必須以不間斷的方式運行,進入同步代碼塊的線程能夠執行Thread.sleep()或者執行Thread.yield()方法,此時它並不釋放對象鎖,只是把運行的機會讓給其餘的線程。 

五、synchronized聲明不會被繼承,若是一個用synchronized修飾的方法被子類覆蓋,那麼子類中這個方法不在保持同步,除非用synchronized修飾。

六、synchronized 關鍵字可以修飾一個對象實例中的函數或者代碼塊。 在一個非靜態方法中 this 關鍵字表示當前的實例對象。 在一個 synchronized 修飾的靜態的方法中,這個方法所在的類使用 Class 做爲實例對象。



再稍作點補充:


關於中斷的四種緣由:

1)    JVM將cpu資源從當前線程切換給其餘線程,使本線程讓出cpu的使用權處於中斷轉態。

2)    線程使用cpu資源期間,執行了sleep( int millsecoond ) 方法, 使當前線程進入休眠狀態,該方法是Thread的一個類方法,線程一旦執行了此方法,就馬上讓出cpu的使用權,使當前線程處於中斷狀態。通過參數指定的時間後,該線程就從新進到線程隊列中排隊等待cpu資源,以便從中斷處繼續運行。

3)    執行了wait()方法,是的當前線程進入等待狀態,等待狀態的前後才能不會主動進到線程隊列中排隊等待cpu資源,必須有其它線程調用notify()方法通知它,使得它從新進到線程隊列中排隊等待cpu資源。

4)    執行了某個操做進入阻塞西黃太,好比讀寫操做。進入阻塞狀態時線程不能進入排隊隊列,只有當引發阻塞的緣由消除時,線程才從新進到線程隊列中排隊等待cpu資源。


死亡: 

處於死亡狀態的線程不具備繼續運行的能力,線程死亡的緣由有二:

1)    執行完了run()中的所有語句,線程完成了它的所有工做。結束了run()方法。

2)    線程被提早強制性的終止,即強制run()方法結束。

所謂死亡狀態就是線程釋放了實體,即釋放分配給線程對象的內存。

相關文章
相關標籤/搜索