JAVA 多執行緒講解
建立多執行緒的方法
建立多執行緒程式的第一種方式:建立Thread類的子類
java.lang.Thread類:是描述執行緒的類,我們想要實現多執行緒的程式,就必須繼承Thread類
實現步驟:
1.建立一個Thread類的子類
2.在Thread類的子類種重寫Thread類中的run方法,設定執行緒任務(開啟執行緒要做什麼)
3.建立Thread類的子類物件
4.呼叫Thread類中的方法start方法,開啟新的執行緒,執行執行緒的run()方法。
void start() 使該執行緒開始執行;Java虛擬機器呼叫該執行緒的run方法。
結果是兩個執行緒併發地執行,當前執行緒(main執行緒)和另外一個執行緒(建立地新執行緒,執行其run方法)。
多次啟動一個執行緒是非法的。特別當執行緒已經結束執行後,不能重新啟動。
java程式屬於搶佔式排程,哪個執行緒的優先順序比較高,哪個執行緒就會優先執行;同一個優先順序,隨機選擇一個執行
建立多執行緒程式的第二種方式:採用Runnable
java.lang.Runnable
Runnable介面應該由那些打算通過某一執行緒執行其真例項的類來實現。類必須定義一個成為run的無引數方法。
java.lang.Thread類的構造方法
Thread(Runnable target)分配新的Thread物件。
Thread(Runnable target, String name) 分配新的Thread物件。
實現步驟:
1.建立一個Runnable介面的實現類
2.在實現類中重寫Runnable介面的run方法,設定執行緒任務。
3.建立一個Runnable介面的實現類物件
4.建立Thread類物件,構造方法中傳遞Runnable介面的實現類物件(控制代碼)
5.呼叫Thread類中的start方法,開啟新的執行緒執行run方法
實現Runnable介面建立多執行緒程式的好處:
1.避免了單繼承的侷限性
一個類只能繼承一個類,類繼承了Thread類就不能繼承其他的類
實現了Runnable介面,還可以繼承其他類,實現了其他的介面
2.增強了程式的可擴充套件性,降低了程式的耦合性(解耦)
實現了Runnable介面方式,把設定執行緒任務和開啟新執行緒進行了分離(解耦)
實現類中,重寫了run方法:用來設定執行緒任務
建立Thread類物件,呼叫start方法,用來開啟新的執行緒
Thread的一些方法
獲取執行緒的名稱:
1.使用Thread類中的方法getName()
string getName() 返回該執行緒的名稱
2.可以先獲取到當前正在執行的執行緒,使用執行緒中的方法getName()獲取執行緒的名稱
static Thread currentThread() 返回當前正在執行的執行緒物件的引用
System.out.printl(Thread.currentThread().getName());
設定執行緒的名稱:
1.使用Thread類中的方法setName(名字)
viod setName(String name)改變執行緒名稱,使之與引數name相同。
2.建立一個帶引數的構造方法。引數傳遞執行緒的名稱;呼叫父類的帶參構造方法,把執行緒名稱傳遞給父類,讓父類(Thread)給子執行緒起一個名字
Thread(String name)分配新的Thread物件。
使執行緒進行短時間的暫停:
public static void sleep(long millis):使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行)。毫秒數結束之後,執行緒繼續執行。
匿名內部類方式實現執行緒的建立
匿名內部類實現執行緒的建立
匿名就是沒有名字的類,內部類是寫在其他類內部的類
匿名內部類作用:簡化程式碼
把子類繼承父類,重寫父類的方法,建立子類物件合一步完成
把實現類,實現類介面,重寫介面中的方法,建立實現類物件合成一步完成
匿名內部類的最終產物是:子類/實現類物件,而這個類沒有名字
格式:
new 父類/介面{
重複父類/介面中的方法
}
new Thread() {
//重寫run方法,設定執行緒任務,執行緒的父類是Thread。 new thread().start();
}.start();
//執行緒的介面是Runnable, RunnableImpl r=new RunnableImpl();
介面等於實現了一個類…
Runnable r=new Runnable() {
方法;
}
new Thread®.start();
執行緒安全問題
當我們使用多個執行緒訪問同一資源的時候,且多個執行緒中對資源有寫的操作,就容易出現執行緒安全問題。要解決上面的多執行緒併發訪問一個資源的安全問題:也就是解決重複票和不存在的票問題,Java中提供了同步機制(Synchronized)來解決。
為了保證每個執行緒都能正常執行原來的操作,Java引入了執行緒同步機制。
有三種方式完成同步操作:
- 同步程式碼塊
- 同步方法
- 鎖機制
同步程式碼塊
同步程式碼塊:synchronized關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
格式如下:
synchronized(鎖物件){
需要同步操作的程式碼
}
同步鎖:
物件的同步鎖只是一個概念,可以想象成在物件上標記一個鎖。
1.鎖物件,可以是任意型別
2.多個執行緒物件,要使用用一把鎖
注意:
1.通過程式碼塊中的鎖物件,可以使用任意的物件
2.但是必須保證多個執行緒使用的鎖物件是用一個
3.鎖物件作用:
把同步程式碼塊鎖住,只讓一個執行緒在同步程式碼中執行
同步技術的原理:
使用一個鎖物件,這個鎖物件也叫做同步鎖,或者是叫物件監視器
3個執行緒一起搶奪cpu的執行權,誰搶到了誰就會去執行自己的run方法來進行賣票
t0搶到了cpu的執行權,執行run方法,遇到synchronized程式碼塊,這個
時候t0會檢查synchronized程式碼塊是否有鎖物件,發現有的話,會獲取
到鎖物件,進入到同步中執行。
t1搶到了cpu的執行權,執行run方法,遇到synchronized程式碼塊,這個
時候t1會檢查synchronized程式碼塊是否有鎖物件,
發現沒有,t1就會進入到堵塞狀態,會一直等待t0執行緒歸還鎖物件,一
直到t0執行緒執行完同步的程式碼,會把鎖物件歸還給同步程式碼塊,t1才能
獲取到鎖物件進入到同步中執行
總結:同步中的執行緒,沒有執行完畢是不會釋放鎖,同步外的執行緒沒有鎖進不去同步。
同步保證了只能由一個執行緒子啊同步中執行共享資料,保證了安全,但是程式頻繁的判斷鎖,釋放鎖程式的效率就會降低。
同步方法
同步方法:使用synchronized修飾的方法,就叫同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法外等候著。
格式:
public synchronized void method(){
//可能會產生執行緒安全問題的程式碼
}
同步鎖是誰:
對於非static方法,同步鎖就是this。
對於static方法,我們使用當前方法所在類的位元組碼物件(類名.class)。
定義方法的原理:
定義一個同步方法,同步方法也會把方法內部的程式碼鎖住,只讓一個執行緒執行,
同步方法鎖住的物件是誰?
就是實現類物件 new RunnableImpl()
也就是this
靜態的同步方法
這個時候的鎖物件就不能是this了,因為this是建立物件之後產生的,靜態方法優於物件,靜態方法的鎖物件是本類的class屬性–>class檔案物件(反射)。
LOCK鎖
解決執行緒安全問題的第三種方法:使用Lock鎖
java.util.concurrent.locks.Lock介面
lock實現了提供了比使用synchronized方法和語句可獲得的更廣泛的鎖操作。
Lock介面中的方法:
void lock()獲取鎖
void unlock()釋放鎖
java.util.concurrent.locks.ReentranLock implement Lock介面
使用步驟:
1.在成員為止建立一個ReentrantLock物件
2.在可能會出現安全問題的程式碼前呼叫Lock介面中的方法lock獲取鎖
3.在可能會出現安全問題的程式碼後呼叫Lock介面中的方法unlock釋放鎖
執行緒狀態
執行緒一共有六種狀態:
NEW 至今尚未啟動的執行緒處於這種狀態
RUNNABLE 正在Java虛擬機器中執行的執行緒處於這種狀態。
BLOCKED 受阻塞並等待某個監視器鎖的執行緒處於這種狀態
WAITTING 無限期地等待另一個執行緒來執行某一特定操作的執行緒處於這種狀態
TIMED_WAITTING 等待另一個執行緒來執行取決於指定等待時間的操作的執行緒處於這種狀態
TERMINATED 已退出的執行緒處於這種狀態
TIMED_WAITTING以及WAITTING需要Object.notify()來進行操作喚醒他們各自的狀態。
執行緒之間的通訊
等待喚醒案例:執行緒之間的通訊
建立一個顧客執行緒(消費者):告知老闆要的包子的種類以及數量,呼叫wait的方法,放棄cpu的執行,進入到WAITTING狀態(無限等地啊)
建立一個老闆執行緒(生產者):掛了5秒鐘做包子,做好包子之後,呼叫notify方法,喚醒顧客吃包子
注意:
顧客和老闆執行緒必須使用同步程式碼塊包裹起來,保證等待和喚醒只能有一個在執行
同步使用的鎖物件必須保證唯一
只有鎖物件才能呼叫wait和notify方法
Object類種的方法
void wait() 在其他執行緒呼叫此物件的notify() 方法或者是 notifyAll() 方法前,導致當前執行緒等待
void notify()
喚醒在此物件監視器上等待的單個執行緒
會繼續執行wait方法之後的程式碼
進入到TimeWaiting(計時等待)有兩種方式:
1.使用sleep(long m)方法,在毫秒值結束之後,執行緒睡醒進入到Runnable/Blocked狀態
2.使用wait(long m)方法,wait方法如果在毫秒值結束之後,還沒有被notify喚醒,就會自動醒來,執行緒進入到Runnable/Blocked狀態
喚醒的方法:
void notify()喚醒此物件監視器上等待的單個執行緒
void notifyA()喚醒在此物件監視器上等待的所有執行緒。