執行緒同步,執行緒狀態,執行緒通訊,執行緒池
同步方法
- 同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法的外面等待著,排隊
- 格式:
public synchronized void method() { // 可能會產生執行緒安全問題的程式碼 }
備註:同步鎖是誰>
對於非static方法,同步鎖就是this
對於static方法,我們使用當前方法所在類的位元組碼物件(類名.class檔案)
同步方法程式碼示例:
@Override public void run() { System.out.println(RunnableImpl.class); System.out.println("this ---->" + this); // 先判斷票是否存在 System.out.println(); while(true){ saleTicket(); } } /* * 靜態的同步方法 * 鎖物件 * 不能是this * this是建立物件之後產生的,靜態方法優先於物件的建立 * 靜態同步方法中的鎖物件是本類的class屬性--->class檔案物件(反射)*/ public static synchronized void saleTicket() { if (ticket > 0) { // 提高賣票的體驗感 ,讓程式睡眠下 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }// 票存在,賣出第ticket張票 System.out.println(Thread.currentThread().getName() + "---->正在售賣第" + ticket + "張票"); ticket--; } }
Lock鎖
java.util.concurrent.lock.Lock機制提供了比synchronize程式碼塊和synchronize同步方法更加廣泛的鎖操作,同步程式碼快/同步方法具有的功能,Lock都有,除此之外更強大,更能體現出面向物件的特徵.
Lock鎖也稱為同步鎖,定義了加鎖與解鎖的動作,方法如下:
- publicvoidlock():加同步鎖
- publicvoidunlock():釋放同步鎖
備註:鎖是控制多個執行緒對共享資源進行訪問的工具.通常,鎖提供了對共享資源的獨佔訪問.一次只能有一個執行緒獲得鎖,對共享資源的所有訪問都需要先獲得鎖.
示例程式碼:
public class RunnableImpl implements Runnable{ //定義一個多執行緒共享的資源 電影票 private int ticket = 100; //1.在成員位置建立一個ReentrankLock物件 Lock Lock = new ReentrantLock(); //設定執行緒的任務:賣票 此時視窗--->執行緒 @Override public void run(){ //先判斷票是否存在 while(true){ //2.在可能引發執行緒安全的程式碼前呼叫Lock介面中的lock方法獲取鎖 Lock.lock(); if(ticket > 0){ System.out.println(Thread.currentThread().getName()+"-->正在售賣第"+ticket+"張票"); ticket--; } finally{ //finally中的語句塊一般都會執行到,用來釋放資源 //釋放鎖 Lock.unlock(); } } } }
執行緒狀態概述
當執行緒被建立並啟動之後,他既不是一啟動就進入到了執行狀態,也不是一直處於執行狀態,在縣城的生命週期中有6中狀態.
執行緒狀態 |
導致狀態發生條件 |
NEW(新建) | 執行緒剛被建立,但是還沒有被啟動,還沒有呼叫start方法 |
RUNNABLE(可執行) | 執行緒可以在java虛擬機器中執行的狀態,可以是正在執行自己的程式碼,也可能沒有,這取決與作業系統處理器 |
BLOCKED(鎖阻塞) | 當一個執行緒試圖獲取一個物件鎖,而該物件鎖被其他執行緒所持有,則該執行緒進入到BLOCKED狀態;當該執行緒持有鎖時,該執行緒就進入到Runnable狀態 |
WAITING(無限等待) | 一個執行緒在等待另一個執行緒執行一個動作(新建)時,該執行緒就進入到Wating狀態,進入這個狀態後是不能自動喚醒的,必須等待另一個執行緒呼叫notify或者notifyAll方法才能都喚醒 |
TIME_WAITING(計時等待) | 同Wating狀態,有幾個方法有超時引數,呼叫他們將進入TimeWaiting狀態,這一狀態建議值保持到超時期滿或者是收到了喚醒通知.帶有超時引數的常用方法有Thread.sleep(),Object.wait |
TERMINATED(被終止) | 因為run方法是正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡 |
狀態描述:
TimeWaiting(計時等待)
TineWiting在JavaAPI中描述為:一個正在限時等待另一個執行緒執行一個(喚醒)動作的執行緒處於這一狀態
其實,當我們呼叫了sleep這一方法之後,當前正在執行的執行緒就進入到了計時等待狀態.
備註:
- 進入到TimeWaiting狀態的一種常見的操作是呼叫sleep方法,單獨的執行緒也可以呼叫,不一定非要有協作關係
- 為了讓其他執行緒有機會執行到,一般建議將Thread.sleep()呼叫放到執行緒run方法內,這樣才能保證該執行緒執行過程中會睡眠
- sleep與鎖無關,執行緒睡眠到期會自動甦醒,並返回到Runnable狀態.sleep()裡面的引數指定的時間是執行緒不會執行的最短時間,因此,sleep()方法不能保證該執行緒睡眠到期後就會立刻開始執行.
Blocked鎖阻塞狀態
Blocked狀態在JavaAPI中的描述為:一個正在阻塞等待一個監視器鎖(鎖物件)的執行緒處於這一狀態.
比如:執行緒A與執行緒B程式碼中使用通一把鎖,如果執行緒A獲取到鎖物件,執行緒A就進入Runnable狀態,反之執行緒B就進入到Blocked鎖阻塞狀態.
Waiting無線等待狀態
Waiting狀態在JavaAPI中的描述為:一個正在無限等待另一個執行緒執行一個特別的(喚醒)動作的執行緒處於這一狀態.
一個呼叫了某個物件的Object.wait()方法的執行緒,會等待另一個執行緒呼叫此物件的Object.notify()或者Object.notifyAll()方法
其實waiting狀態他並不是一個執行緒的操作,它體現的十多個執行緒之間的通訊,可以理解為多個執行緒之間的協作關係,多個執行緒會爭取鎖,同時相互之間又存在協作關係.
等待喚醒機制
執行緒間通訊
概念:多個執行緒在處理同一個資源,但是處理的動作(執行緒的任務)卻又不相同.
比如說,執行緒A用來生產一個飲料,執行緒B用來消費,飲料可以理解為同一資源,執行緒A與執行緒B處理的動作,一個是生產,一個是消費,name執行緒A與執行緒B之間就存線上程通訊問題.
為什麼要處理執行緒之間的通訊:
多個執行緒併發在執行時,在預設情況下CPU是隨機切換執行緒的,當我們需要多個執行緒共同來完成一件任務時,並且幫助我們達到多執行緒共同操作一份資料.
如何保證執行緒間通訊有效利用資源:
多個執行緒在處理同一個資源的時候,並且任務還不相同,需要執行緒通訊來幫助我們解決執行緒之間對同一個變數的使用或者操作.就是多個執行緒在操作同一份資料時,避免對同一共享變數的爭奪,也就是我們需要通過一定的手段使各個執行緒有效的利用資源.
而這種手段就是----->等待喚醒機制.
等待喚醒機制
什麼是等待喚醒機制呢?
這是多個執行緒間的一種協作機制.
就是一個執行緒進行了規定操作之後,就進入到了等待狀態(wait()),等待其他執行緒執行完他們指定程式碼後,再將其喚醒(notify());
在有多個執行緒進行等待時,如果需要,可以使用notifyAll()來喚醒所有的等待執行緒.
wait/notify就是執行緒間的一種協作機制.
等待喚醒中的方法:
等待喚醒機制就是用來解決執行緒間通訊問題的.可以使用到的方法有三個,如下:
- wait():執行緒不再活動,不再參與排程,進入到waitset中,因此不會浪費CPU資源,也不再去競爭鎖,這時的執行緒就是WAITYING.他還要等著別的執行緒執行一個特別的動作,就是喚醒通知(notify)在這個物件上等待的執行緒從waitset中釋放出來,重新進入到排程佇列(readyqueue)中.
- notify():選取說通知物件的waitset中的一個執行緒釋放.例如:餐廳有空位置後,等候最久的顧客最先入座
- notifyAll():釋放所通知物件的waitset中的全部執行緒.
備註:
哪怕只通知了一個等待執行緒,被通知的執行緒也不能立即回覆執行,因為他當初中斷的地方是在同步程式碼快中,而此刻他已經不持有鎖了,所以他需要再次嘗試著去獲取鎖(很可能面臨著其他執行緒的競爭),成功後才能在當初呼叫wait方法之後的地方恢復執行.
總結如下:
如果能獲取到鎖,執行緒就從WAITING狀態變成RUNNABLE狀態
否則,從waitset中出來,又進入set中,執行緒就從WAITING狀態轉變成BLOCKED狀態.
呼叫wait和notify方法的注意細節:
- wait方法與notify方法必須由同一個鎖物件呼叫.因為,對用的鎖物件可以通過notify喚醒使用同一個鎖物件呼叫的wait方法後的執行緒
- wait方法與notify方法是屬於Object類的方法的.因為,鎖物件可以是任意物件,而任意物件的所屬類都是集成了Object類的.
- wait方法與notify方法必須要在聽不程式碼塊或者同步方法中使用.因為,必須通過鎖物件呼叫這兩個方法來實現等待與喚醒.
執行緒池
執行緒池的概念
執行緒池:其實就是一個可以容納多個執行緒的容器,其中的執行緒可以反覆的使用,省去了頻繁的建立執行緒物件的操作,無需反覆建立執行緒而消耗過多的系統資源.
由於執行緒池中有很多操作都是與優化系統資源有關的,我們今天先來介紹下執行緒池的工作原理
合理利用執行緒池能夠帶來什麼樣的好處:
- 降低資源消耗,減少了執行緒的建立與銷燬的次數,每個工作執行緒都可以被重複利用,可執行多個任務
- 提高了響應速度.當任務到達時,任務可以不需要等待執行緒的建立就能立即執行.
- 提高了執行緒的可管理性.可以根據系統的承受能力,調整執行緒池中工作執行緒的數目,防止因為消耗過多的記憶體,而導致伺服器的宕機(每個執行緒需要大約1MB記憶體,執行緒開的越多,消耗的記憶體也就越大,宕機的風險也就更高).
執行緒池的使用
java裡面執行緒池的頂級介面是java.util.concurrent.Executor ,但嚴格意義講,Executor它並不是一個執行緒池,它只是執行執行緒的一個工具,真正的執行緒池介面是java.util.concurrent.ExecutorSrevice.
因此java.utilconcurrent.Executors執行緒工程類童工了一些靜態工廠,生成一些常用的執行緒池.官方建議使用Executors來建立執行緒池物件.
Executors建立執行緒池的方法如下:
- publicstaticExecutorSrevicenewFixedThreadPool(intnThreads):返回的就是執行緒池物件.(建立的是有界的執行緒池,也就是執行緒池中的執行緒個數可以指定最大數量).
獲取到了一個執行緒池ExecutorSrevice物件,在該類中定義了一個使用執行緒池物件的方法如下:
public Future<?>submit(Runnabletask) :獲取執行緒池中的某一個執行緒物件,並執行.
- Future介面 :用來記錄執行緒任務執行完畢後產生的結果.執行緒的建立與使用.
使用執行緒池中執行緒物件的步驟:
- 建立執行緒池物件
- 建立Runnable介面子類物件.(task)
- 提交Runnable介面子類物件.(taketask)
- 關閉執行緒池(一般不做).