多執行緒——基礎
程式:用語言編寫的一組指令的集合,指一段靜態的程式碼
程序:程式的一次執行過程,或是正在執行的一個程式
執行緒:一個程式內部的一條執行路徑
一個程序可以有多個執行緒
2.實現多執行緒的方式
繼承Thread類
public class TestThread1 extends Thread{
Thread實現了Runnable介面
不建議使用:避免OOP單繼承侷限性
實現Runnable介面
package com.yl.demo;
public class TestThread2 implements Runnable{
private int id;
public TestThread2() {
}
public TestThread2(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
推薦使用:避免單繼承侷限性,靈活方便,方便同一個物件被多個執行緒使用
對比thread和runnable
相同點:都要重寫run方法
不同點:Thread不適合資源共享,Runnable適合,使用Runnable,可以避免java中的單繼承限制,程式碼可以被多個執行緒共享,程式碼和資料獨立;執行緒池只能彷彿實現Runnable或Callable類執行緒,不能直接放入繼承Thread的類
實現Callable介面
-
需要返回值型別
-
重寫call方法,需要丟擲異常
-
建立目標物件
-
建立執行服務
-
提交執行
-
獲取結果
-
關閉服務
package com.yl.demo;
import java.util.concurrent.*;
public class TestThread3 implements Callable {
private int id;
public TestThread3(int id){
this.id = id;
}
好處:可以定義返回值、可以丟擲異常
使用執行緒池
3.對比Thread和Runnable建立方式:
相同點:都要重寫run方法
不同點:Thread不適合資源共享,Runnable適合,使用Runnable,可以避免java中的單繼承限制,程式碼可以被多個執行緒共享,程式碼和資料獨立;執行緒池只能彷彿實現Runnable或Callable類執行緒,不能直接放入繼承Thread的類
4.執行緒狀態
初始(NEW):新建立了一個執行緒物件,但還沒有呼叫start()方法。
執行(RUNNABLE):Java執行緒中將就緒(ready)和執行中(running)兩種狀態籠統的稱為“執行”。 執行緒物件建立後,其他執行緒(比如main執行緒)呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,等待被執行緒排程選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的執行緒在獲得CPU時間片後變為執行中狀態(running)。 阻塞(BLOCKED):表示執行緒阻塞於鎖。 等待(WAITING):進入該狀態的執行緒需要等待其他執行緒做出一些特定動作(通知或中斷)。 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。 終止(TERMINATED):表示該執行緒已經執行完畢。
這6種狀態定義在Thread類的State列舉中,可檢視原始碼進行一一對應。
5.執行緒休眠
-
sleep(時間)指定當前執行緒阻塞的毫秒數
-
sleep存在異常InterruptedException
-
sleep時間達到後執行緒進入就緒狀態
-
sleep可以模擬網路延時倒計時
-
每一個物件都有鎖,執行緒不會釋放鎖
sleep()和wait()
sleep是執行緒類(Thread)的方法,導致此執行緒暫停執行指定時間,把執行機會給其他執行緒,但是監控狀態依然保持,到時後會自動恢復。呼叫sleep不會釋放物件鎖。 wait是Object類的方法,對此物件呼叫wait方法導致本執行緒放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此物件發出notify方法(或notifyAll)後本執行緒才進入物件鎖定池準備獲得物件鎖進入執行狀態。wait宣告在同步程式碼塊或同步方法中
6.執行緒禮讓
-
讓當前正在執行的執行緒暫停,但不阻塞
-
將執行緒從執行狀態轉為就緒狀態
-
讓cpu重新排程,禮讓不一定成功,取決於cpu
7.執行緒停止
-
建議執行緒正常停止——>利用次數,不建議死迴圈
-
建議使用標誌位——>設定一個標誌位boolean
-
不要用stop或destroy
8.Join
-
join合併執行緒,待此執行緒執行完成後,再執行其他執行緒,其他執行緒阻塞
-
可以想象成插隊
9.執行緒優先順序
-
優先順序範圍1-10
-
優先順序越高,分配的資源越多
-
setPriority(int xxx)
-
getPriority()
10.守護執行緒
-
執行緒分為使用者執行緒和守護執行緒
-
虛擬機器必須保證使用者執行緒執行完畢
-
虛擬機器不用等待守護執行緒執行完畢
-
如:後臺記錄操作日誌、監控記憶體、垃圾回收等待
Thread thread = new Thread(god);
thread.setDaemon(true); //預設是false
11.執行緒同步
多個執行緒操作同一個資源
併發:同一個物件被多個執行緒同時操作
加入鎖機制synchronized
關鍵字synchronized可以保證在同一時刻,只有一個執行緒可以執行某個方法或某個程式碼塊,同時synchronized可以保證一個執行緒的變化可見(可見性),即可以代替volatile。
synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行就獨佔該鎖,直到該方法返回才釋放鎖。
當一個執行緒獲得物件的排他鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可,存在一下問題:
-
一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起
-
多執行緒競爭下,加鎖釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題
-
如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能問題
Java中每一個物件都可以作為鎖,這是synchronized實現同步的基礎:
-
方法裡需要修改的內容才需要鎖,鎖太多會浪費資源
-
普通同步方法(例項方法),鎖是當前例項物件this ,進入同步程式碼前要獲得當前例項的鎖
-
靜態同步方法,鎖是當前類的class物件 ,進入同步程式碼前要獲得當前類物件的鎖
-
同步方法塊,鎖是括號裡面的物件,對給定物件加鎖,進入同步程式碼庫前要獲得給定物件的鎖。
-
同步方法仍然涉及到同步監視器(鎖),只是不需要我們顯示的宣告
synchronized(同步監視器) {
需要同步執行的程式碼片段
}
同步監視器是java中任意的一個物件,只要保證多個執行緒看到的該物件是”同一個“,即可保證同步塊中的程式碼是併發安全的
可重入實現:
每個鎖關聯一個執行緒持有者和一個計數器。當計數器為0時表示該鎖沒有被任何執行緒持有,那麼任何執行緒都都可能獲得該鎖而呼叫相應方法。當一個執行緒請求成功後,JVM會記下持有鎖的執行緒,並將計數器計為1。此時其他執行緒請求該鎖,則必須等待。而該持有鎖的執行緒如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增。當執行緒退出一個synchronized方法/塊時,計數器會遞減,如果計數器為0則釋放該鎖。
Lock
-
通過顯式定義同步鎖物件來實現同步,同步鎖使用Lock物件充當
-
ReentranLock(可重入鎖)實現了Lock,可以顯示加鎖、釋放鎖
synchronized和Lock的對比
-
Lock是顯示鎖,synchronized是隱式鎖,出了作用域自動釋放
-
Lock只有程式碼塊鎖,synchronized有程式碼塊鎖和方法鎖
-
使用Lock鎖,JVM將花費較少的時間來排程執行緒,效能更好,具有更好的擴充套件性(提供更多的子類)
-
優先使用順序:
-
Lock>同步程式碼塊(已經進入了方法體,分配了相應資源)>同步方法(方法體之外)
-
-
Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;
-
synchronized在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;
-
Lock可以讓等待鎖的執行緒響應中斷,而synchronized卻不行,使用synchronized時,等待的執行緒會一直等待下去,不能夠響應中斷;通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
執行緒池
提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中,可以避免頻繁建立銷燬、實現重複利用
好處:
-
提高響應速度
-
降低資源消耗
-
便於執行緒管理:
-
corePoolSize:核心池的大小
-
maxPoolSize:最大執行緒數
-
keepAliveTime:執行緒沒有任務時最多保持多長時間後終止
-
執行緒池相關API: ExecutorService和Executors
-
ExecutorService:真正的執行緒池介面,常見子類ThreadPoolExecutor
-
Executors:工具類、執行緒池的工廠類,用於建立並返回不同型別的執行緒池
package com.yl.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPool {
public static void main(String[] args) {
//1.建立服務,建立執行緒池
//newFixedThreadPool:引數為執行緒池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//2.執行
service.execute(new myThread());
service.execute(new myThread());
service.execute(new myThread());
//3.關閉連線
service.shutdown();
}
}
class myThread implements Runnable{