6、多執行緒
6、多執行緒
6.1 基本概念
程式,程序,執行緒
程式(program)是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的程式碼,靜態物件。
程序(process)是程式的一次執行過程,或是正在執行的一個程式。是一個動態的過程:有它自身的產生、存在和消亡的過程。——生命週期
如:執行中的QQ,執行中的MP3播放器
程式是靜態的,程序是動態的
程序作為資源分配的單位,系統在執行時會為每個程序分配不同的記憶體區域執行緒(thread),程序可進一步細化為執行緒,是一個程式內部的一條執行路徑。
若一個程序同一時間 並行執行多個執行緒,就是支援多執行緒的
執行緒作為排程和執行的基本單位,每個執行緒擁有獨立的執行棧和程式計數器(pc),執行緒切換的開銷小
一個程序中的多個執行緒共享相同的記憶體單元/記憶體地址空間
它們從同一堆中分配物件,可以訪問相同的變數和物件。這就使得執行緒間通訊更簡便、高效。但多個執行緒操作共享的系統資源可能就會帶來 安全的隱患
-
一個Java應用程式java.exe 至少有三個執行緒:main()主執行緒,gc() 垃圾回收執行緒,異常處理執行緒;
-
並行與併發:
並行:多個CPU同時執行多個任務。比如:多個人同時做不同的事。
併發:一個CPU(採用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事。
對於多執行緒:
- 以單核CPU為例,只使用單個執行緒先後完成多個任務(呼叫多個方法) 反而比用多個執行緒
來完成用的時間更短。 ---> 執行緒之間的切換需要時間多執行緒程式的優點:
提高應用程式的響應。對圖形化介面更有意義,可增強使用者體驗。
提高計算機系統CPU的利用率
改善程式結構。將既長又複雜的程序分為多個執行緒,獨立執行,利於理解和修改何時需要多執行緒:
程式需要同時執行兩個或多個任務。
程式需要實現一些需要等待的任務時,如使用者輸入、檔案讀寫操作、網路操作、搜尋等。
需要一些後臺執行的程式時
6.2 執行緒的建立和使用*
多執行緒的建立
方式一:繼承Thread類
建立一個繼承於Thread類的子類
重寫Thread類的run( ) --> 將此執行緒執行的操作宣告在run( )中
建立Thread類的子類的物件
通過此物件呼叫start( )---> a.啟動當前執行緒 b.呼叫當前執行緒的run( )
//1.建立一個繼承於Thread類的類 class MyThread extends Thread{ //2.重寫Thread類的run() pbulic void run(){ sout("這個執行緒要執行的操作"); } } //main--主執行緒 public void main(){ //3.建立Thread類的子類的物件 MyThread t1 = new MyThread(); //4.通過此物件呼叫start() t1.start(); new Thread(){ public void run(){ sout("多執行緒--Thread匿名子類"); } }.start(); }
注意:
- 要想啟動多執行緒,必須呼叫start( ),呼叫run()並不會啟動多執行緒
- 一個執行緒物件只能呼叫一次start()方法啟動
方式二:實現Runnable介面
建立一個實現 Runnable介面的類 --實現類
實現類實現介面中的 抽象方法:run( )
建立實現類的物件
將此物件作為引數 傳遞到Thread類的構造器中,建立Thread類物件 --代理類
通過Thread類的物件 呼叫start( )
//1.建立一個實現了 Runnable介面的類,並實現run()方法 class MyThread implements Runnable{ pbulic void run(){ sout("此執行緒要執行的操作") }; } public void main(){ //2.建立實現類物件 MyThread w = new MyThread(); //3.將實現類物件作為引數 傳遞到Thread類的構造器中,建立Thread類物件 Thread t1 = new Thread(w); Thread t2 = new Thread(w); //4.通過Thread類物件 掉喲start() t1.start(); t2.start(); }
小結:
開發中,優先選擇 實現Runnable介面的方式:
沒有單繼承的侷限性
多個執行緒可以共享實現類中的資料。聯絡:public class Thread implements Runnable
相同點:兩種方式都需要重寫run( ),將執行緒要執行的邏輯宣告在run( )中。
執行緒方法
Thread中的常用方法:
start( ) : 啟動當前執行緒,呼叫當前執行緒的run( )
run( ) : 通常需要重寫Thread類中的此方法,將建立的執行緒要執行的操作宣告在run( )中
currentThread( ):靜態方法,返回當前執行程式碼的執行緒
getName( ):獲取當前執行緒的名字
setName( ):設定當前執行緒的名字 【也可以在 構造器中設定執行緒名字
new MyThread("執行緒1")
】yield( ):釋放當前cpu的執行權
join( ):線上程a中呼叫執行緒b的join( ),此時執行緒a進入阻塞,直到b執行完畢。
stop( ):強制結束當前執行緒 (已過時)
sleep(long millitime):讓當前執行緒“睡眠”指定 毫秒,睡眠期間此執行緒 阻塞。
isAlive( ):判斷當前執行緒是否存活。
執行緒的優先順序:
MAX_PRIORITY: 10
MIN_PRIORITY: 1
NORM_PRIORITY:5 -->預設優先順序getPriority( ):獲取當前執行緒優先順序
setPriority( ):設定執行緒的優先順序
--- 優先順序只是提高被cpu執行的概率
執行緒通訊
wait( ),notify( ),notifyAll( ):此三個方法定義在Object類中
執行緒的分類
Java中的執行緒分為兩類:守護執行緒 和 使用者執行緒
虛擬機器JVM 必須確保 使用者執行緒執行完畢才能離開 ,若JVM中都是守護執行緒,當前JVM退出。
使用者執行緒完畢--JVM關閉--守護執行緒完畢
守護執行緒是用來服務使用者執行緒的,通過在start( )前呼叫
thread.setDaemon(true)
可以設定為守護執行緒守護執行緒: 後臺記錄操作日誌,監控記憶體,垃圾回收等待
6.3 執行緒的生命週期
- JDK 中用Thread.State 類定義了 執行緒的5種狀態
-
Thread.State (返回列舉型別值) : ( 新建
NEW
,就緒WAITING
,執行RUNNABLE
,阻塞BLOCKED
,死亡TERMINATED
)
6.4 執行緒同步*
執行緒同步的問題:當執行緒a操作某共享資料的過程中(還沒操作完),程序b參與進來也操作該共享資料,就會造成共享資料的錯誤。
解決: 當一個執行緒a在操作共享資料時,其他執行緒不能參與進來,直到執行緒a操作完畢。
問題:同步後,該同步片段變為 單執行緒;同步片段過多可能使程式出現錯誤 --被某一執行緒全部佔用。
synchronized
方式一:同步程式碼塊
synchronized(同步監視器){ //需要被同步的程式碼 }
說明:a.操作共享資料的程式碼,即為需要被同步的程式碼。
b.共享資料:多個執行緒共同操作的變數
c.同步監視器,俗稱 鎖。任何一個類的物件 都可以充當鎖
要求:多個執行緒必須要共用同一把鎖。--Runnable方式中可以用 this
--Thread方式中可以用 xx.class【類物件】
方式二:同步方法
若操作共享資料的程式碼完整的宣告在一個方法中,可以考慮使用同步方法
public synchronized void method(){ //被同步的程式碼(實現Runnabe方式)-- this } public static synchronized void method(){ //繼承Thread方式-- 類.class }
總結:同步方法仍然涉及到同步監視器,只是不再需要顯示宣告。(預設)
非靜態的同步方法,同步監視器是:this
靜態的同步方法,同步監視器是:當前類本身
Lock鎖
方式三:Lock鎖
JDK5.0新增,手動上鎖,解鎖
//1.例項化ReentrantLock privete ReentrantLock lock = new ReentrantLock(); //ReentrantLock(true)-公平鎖 //2.呼叫上鎖方法 lock() lock.lock(); ---------------------要同步的程式碼片段 //3.呼叫解鎖方法 unlock() lock.unlock();
synchronized 與 Lock 的對比
面試題:synchronized 與 Lock的異同?
相同:二者都可以解決執行緒安全問題
不同:synchronized機制在執行完相應的程式碼片段後,自動釋放同步監視器
Lock需要手動的啟動同步
lock()
,手動的結束同步unlock()
優先順序:Lock---> 同步程式碼塊 ---> 同步方法
執行緒死鎖
死鎖
不同的執行緒分別佔用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了執行緒的死鎖
出現死鎖後,不會出現異常,不會出現提示,只是所有的執行緒都處於阻塞狀態,無法繼續解決方法
專門的演算法、原則
儘量減少同步資源的定義
儘量避免巢狀同步
6.5 執行緒通訊
synchronized(this){
notify(); //執行緒a 釋放(一個)被阻塞的執行緒b
/*
同步程式碼塊
*/
wait(); //當前執行緒(a)進入阻塞,並釋放同步監視器 --- 交替到執行緒b
}
執行緒通訊涉及到的三個方法:
wait( ): 當前執行緒進入阻塞狀態,並釋放同步監視器
notify( ): 喚醒被wait的一個執行緒 (若有多個執行緒被wait 則喚醒優先順序高的那個)
notifyAll( ):喚醒所有被wait的執行緒說明(對於wait, notify, notifyAll三個方法):
必須在 同步程式碼塊或同步方法中使用。
呼叫者必須是同步程式碼塊或同步方法中的 同步監視器。
都是 定義在java.lang.Object類中的。
面試題:sleep( ) 和 wait( ) 的異同?
相同點:一旦執行方法,都可以使得當前的執行緒進入阻塞狀態。
不同點: 1.兩個方法宣告的位置不同:Thread類中宣告sleep(),Object類中宣告wait()
2.呼叫的要求不同:sleep()可以在任何場景下呼叫,wait()必須使用在同步程式碼塊/同步方法中。
3.是否釋放鎖:若兩個方法都在同步程式碼塊/同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
生產者消費者模式:
管程法 (生產者---緩衝區---消費者)
訊號燈法 (標誌位)
生產者消費者 共用同一把鎖
public synchronized void produce(){ if(goods < 20){ goods++; sout("生產產品"); nofity(); //喚醒消費者,通知消費 }else{ wait(); //等待消費 } } public synchronized void consum(){ if(goods > 0){ goods--; sout("消費產品"); nofity(); //喚醒生產者,通知生產 }else{ wait(); //等待生產 } }
6.6 新增執行緒建立方式
JDK5.0新增了兩種建立執行緒的方式 ---(共四種建立執行緒的方式)
執行緒建立方式三:實現Callable介面
1.建立一個實現 Callable介面 的實現類
2.實現介面中的call( ) --> 將此執行緒執行的操作宣告在call( )中,可以有返回值
3.建立實現類物件
4.建立FutureTask物件,將實現類物件 傳入 FutureTask構造器中 ---(FutureTask 實現了Runnale介面)
5.建立Thread物件,將FutureTask物件 傳入 Thread構造器中
6.通過Thread物件 呼叫 start( )
---> futureTask.get( )可以獲得 call( )的返回值//1.建立一個實現 Callable介面 的實現類,實現call() class myThread implements Callable{ public Object call() throws Exception{ sout("執行緒要執行的操作"); return null; //執行緒的返回值 } } public void main(){ //2.建立實現類物件 myThread w = new myThread(); //3.建立FutureTask物件,將實現類物件 傳入 FutureTask構造器中 FutureTask futureTask = new FutureTask(w); //4.建立Thread物件,將FutureTask物件 傳入 Thread構造器中,呼叫start() new Thread(futureTask).start(); Object ob = futureTask.get();//5.futureTask.get()可以獲得 call()的返回值 }
實現Callable介面的方式 比 實現Runnable介面方式 建立多執行緒更強大?
call( )可以有返回值
call( )可以丟擲異常,被外面的操作捕獲,獲取異常的資訊
Callable是支援泛型的
執行緒建立方式四:執行緒池
提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,用完放回。
- 提高相應速度,降低資源消耗(避免重複的建立銷燬,實現重複利用)
- 便於執行緒管理:
corePoolSize:核心池的大小
maximumPoolSize:最大執行緒數
keepAliveTime:執行緒沒有任務時最多保持多長時間後會終止
執行緒池的建立:
1.提供指定執行緒數量的執行緒池
2.執行指定的執行緒的操作,需要提供 實現Runnable介面/Callable介面的實現類物件
execute
(Runnable實現類物件) /submit
(Callable實現類物件)3.關閉連線池
shutdown()
class MyRunThread implements Runnable{ public void run(){}; } class MyCalThread implements Callable{ public Object cal(){}; } public void main(){ //1.提供指定數量的執行緒池 ExecutorService service = Executors.newFixedThreadPool(8); //2.執行指定執行緒的操作--管理執行緒 service.execute(new MyRunThread() ); //適合 Runnable service.submit(new MyCalThread() ); //適合 Callable service.setXXX; //執行緒管理 //3.關閉連線池 service.shutdown(); }