高併發程式設計之基礎概念(二)
在上一篇中,簡單介紹了一寫執行緒的基礎知識,以及一些概念,本文繼續介紹一些基礎知識以及一些方法。
什麼是執行緒
執行緒,有時被稱為輕量程序,它是程序內的執行單元。執行緒是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點兒在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。一個執行緒可以建立和撤消另一個執行緒,同一程序中的多個執行緒之間可以併發執行。由於執行緒之間的相互制約,致使執行緒在執行中呈現出間斷性。執行緒也有就緒、阻塞和執行三種基本狀態。就緒狀態是指執行緒具備執行的所有條件,邏輯上可以執行,在等待處理機;執行狀態是指執行緒佔有處理機正在執行;阻塞狀態是指執行緒在等待一個事件(如某個訊號量),邏輯上不可執行。每一個程式都至少有一個執行緒,若程式只有一個執行緒,那就是程式本身。(來自百度百科)
執行緒從new出來呼叫start方法,此時執行緒並不是直接開始執行,而是進入runnable狀態,當執行緒競爭臨界區失敗時,進入blocked狀態,競爭成功則開始執行。當執行緒被呼叫wait方法時進入等待狀態,再次呼叫notify方法時繼續執行。也可以設定wait的時間,如果時間內每有呼叫notify也可以繼續執行。當執行緒呼叫結束之後進入Treminated狀態。
1) 新建(new):執行緒被建立後的初始狀態
2) 就緒(runnable):呼叫start()方法,進入“就緒”狀態
3) 執行(running):進入“就緒”狀態後,獲取到cpu分配時間進入“執行狀態”
4) 阻塞/等待(blocked/*waiting):
a.執行緒需要等待某個條件時,而條件未成熟時,等待排程器通知,進入‘等待’狀態
b.獲取某個資源時,資源被加鎖,等待資源被釋放,進入‘阻塞’狀態
5) 死亡(terminated):run()執行完畢或因異常導致退出,執行緒死亡
執行緒的基本操作
- 新建執行緒
繼承Thread類,重寫run方法。
//直接重寫run方法 Thread t = new Thread() { @Override public void run() { // TODO Auto-generated method stub } }; t.start();
實現Runnable介面,實現run方法
//實現Runnable介面 Runnable runnable = new Runnable() { public void run() { // TODO Auto-generated method stub } }; Thread t = new Thread(runnable); t.start();
其中,執行緒中start方法與run方法是有區別的。start方法執行後,執行緒進入runnable狀態,等待執行run方法。而直接在主執行緒中呼叫run方法,其實是直接同步執行run方法,並沒有重新另啟執行緒。
- 執行緒終止
Thread t = new Thread(runnable); t.stop();
此方法可以直接使執行緒結束,並且釋放所有資源,但是這個方法有一個非常明顯的錯誤,會導致資料不一致性。所有現在此方法已經被棄用,不推薦使用。
- 執行緒中斷
1. interrupt方法 給程式打中斷標記
2. isInterrupted方法 判斷程式是否存在中斷標記
Runnable runnable = new Runnable() { public void run() { while(true) { if (Thread.currentThread().isInterrupted()) { //TODO: break; } } } }; Thread t = new Thread(runnable); t.start(); t.interrupt();
上面程式碼中,t執行緒在執行執行interrupt方法之後,在run方法中,執行迴圈之前就會先去判斷是否有打中斷標,如果有中斷標則停止。這樣做的好處就在於不會想stop方法一樣,中斷方法會導致資料不一致,而這個方法則可以由程式設計師來控制中斷程式的位置,這樣就可以避免資料產生不一致性。
3. InterruptedException異常
在查詢中,一些sleep,wait,join等會使得程式等待的方法都會丟擲一個InterruptedException異常,這是因為我們線上程等待時,呼叫了interrupt方法。這樣我們也可以去控制執行緒停止,但是,有個問題就是,在丟擲異常之後,會將執行緒中的中斷標識清除,如果想要程式停止,則需要我們在catch中去再將中斷標識打上。
Runnable runnable = new Runnable() { public void run() { while(true) { if (Thread.currentThread().isInterrupted()) { //TODO: break; } try { Thread.sleep(2000); } catch (InterruptedException e) { //TODO: Thread.currentThread().interrupt(); } } } }; Thread t = new Thread(runnable); t.start(); t.interrupt();
interrupted方法 返回執行緒是否存在中斷標識,並立即重置狀態。與isInterrupted類似,但是isInterrupted方法不會清除中斷標識。
-
執行緒掛起(suspend)和繼續執行(resume)執行緒
suspend可以使執行緒處於掛起狀態,但是不會去釋放資源,當再次呼叫resume方法時,執行緒繼續執行,但是這兩個方法是不建議使用的,因為這兩個方法會導致死鎖。因為多執行緒之間執行對於執行緒之間的排程,我們是由作業系統完成的,我們無法控制,如果呼叫resume方法的執行緒先於呼叫suspend方法的執行緒執行,這樣就會導致執行緒死鎖,程式無法繼續往下執行。 -
謙讓 yeild
yield 方法是說,讓當前執行緒放棄當前cpu時間片段,然後和其他執行緒一起公平競爭cpu。 -
等待執行緒結束 join
join方法可以等待某個執行緒執行結束,如果在B執行緒中呼叫A執行緒的join方法,則是B執行緒中剩下的步驟需要等待A執行緒結束之後才可以繼續執行。
守護執行緒
在後臺默默的完成一些系統性能的服務,比如垃圾回收,JIT執行緒等都是守護執行緒。如果在一個程式中Java虛擬機發現,應用中執行的只有守護執行緒了,那麼虛擬機器就會退出程式。
我們可以設定一個執行緒的Daemon屬性,設定為true,就表示這個執行緒是一個守護執行緒,但是這個設定一定要做start之前設定,否則是不會生效的。設定Daemon屬性為true之後,一旦其他執行緒執行結束,整個程式就結束了,此時不管t執行緒是否執行結束,jvm都會將程式結束。
Thread t = new Thread() { @Override public void run() { // TODO Auto-generated method stub } }; t.setDaemon(true); t.start();
執行緒優先順序
執行緒是可以設定優先順序的,但是需要注意的是優先順序高的執行緒並不一定是優先執行。優先順序高的執行緒只是在cpu排程時更有可能競爭勝利,搶佔到資源執行。
執行緒優先順序的設定方法為設定Priority屬性,這個屬性可以傳入一個int型別的引數,引數最大為10,最小為1.而且Thread也提供了3個常量分別為Thread.MAX_PRIORITY=10,Thread.NORM_PRIORITY=5,Thread.MIN_PRIORITY=1
我們看一個例子:
public static void main(String[] args) { Thread t1 = new Thread() { @Override public void run() { for (int i = 0; i < 100000000; i++) { synchronized (Test.class) { } } System.out.println("t1 end"); } }; Thread t2 = new Thread() { @Override public void run() { for (int i = 0; i < 100000000; i++) { synchronized (Test.class) { } } System.out.println("t2 end"); } }; t1.setPriority(Thread.MIN_PRIORITY); t2.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.start(); }
在這個例子中,t1和t2不停的去競爭一個資源,這時優先順序更高的t2就更有可能獲取到資源並執行,所以在我的例項中t2在多數情況下首先執行結束並且打印出end語句,但是也會有是出現t1首先執行結束,列印end語句。這就更有力的說明了優先順序高的執行緒只是競爭能力強與優先順序低的執行緒,但不一定是絕對優先的。
基本的執行緒同步操作
- synchronized 關鍵字
synchronized關鍵字是jvm內建的關鍵字,它的實現都是在jvm內部實現的。下面說說synchronized 關鍵字的用法。
- 指定物件加鎖:對給定物件加鎖,進入同步程式碼前要獲取給定物件的鎖。例如:
public void testSynchronized() {
Object obj = new Object(); synchronized (obj) { ...... } }
上面方法中寫了一個程式碼塊,而synchronized將obj作為鎖,將下面程式碼塊進行鎖定。如果要想執行下面程式碼塊,就必須獲取到obj這個例項的鎖。
- 直接作用於例項方法:相當於對當前例項加鎖,進入同步程式碼前要獲得當前例項的鎖。例如:
public synchronized void testSynchronized() { Object obj = new Object(); }
上面方法中將synchronized定於在了方法上,我們來看呼叫。
public static void main(String[] args) { Test test = new Test(); test.testSynchronized(); }
我們在呼叫時需要建立Test的例項test,而且想要執行這個方法,必須要先獲取到test的鎖才可以執行。
- 直接作用於靜態方法:相當於對當前類加鎖,進入同步程式碼前要獲得當前類的鎖。例如:
public static synchronized void testSynchronized() { Object obj = new Object(); }
上面方法中將synchronized定於在了靜態方法上,我們來看呼叫。
public static void main(String[] args) { Test.testSynchronized(); }
我們在呼叫時直接使用Test.testSynchronized()來執行,但是在執行前也必須先獲取到Test.class的鎖才可以執行。
- wait方法 notify方法
- wait方法
wait方法是讓一個執行緒進入等待狀態,我們看下面例子:
Object obj = new Object(); synchronized (obj) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
在這個例子中我們讓這個執行緒進行進行等待,但是這個執行緒此時會將已經持有的臨界區資源釋放,並進入等待狀態。而且臨界區資源可以繼續被其他執行緒所競爭。而且需要注意的是呼叫wait方法時必須持有臨界區資源,也就是obj的監視器,否則會丟擲異常。
- notify方法
notify方法是讓擁有監視器的執行緒隨機釋放一個繼續執行,它還有一個方法叫做notifyAll,它可以釋放所有擁有呼叫這個方法的執行緒的監視器的執行緒繼續執行。 我們看下面例子:
Object obj = new Object(); synchronized (obj) { try { obj.notify(); } catch (InterruptedException e) { e.printStackTrace(); } }
這個例子中呼叫了notify方法。所有在所有擁有obj監視器的執行緒將會隨機有一個執行緒繼續執行,但是不會立刻執行,因為此時這個執行緒還沒有競爭到臨界區的資源,要等到這個執行緒競爭到資源之後才會繼續執行。而且呼叫notify方法的執行緒也必須先獲取到obj的監視器才可以呼叫這個方法,否則將丟擲異常。需要注意的是呼叫notify方法是隨機讓一個執行緒繼續執行。所以這個方法要謹慎使用。
-------------------- END ---------------------
最後附上作者的微信公眾號地址和部落格地址
公眾號:wuyouxin_gzh
Herrt灬凌夜:https://www.cnblogs.com/wuyx/