1. 程式人生 > 實用技巧 >JavaSE高階-多執行緒

JavaSE高階-多執行緒

一、概念:
1.程序:
在系統中獨立執行的程式,直接分配佔用系統的CPU、磁碟、網路等資源。
程序也是程式執行的一次過程,是系統執行程式的基本單位。
程序中至少要有一個主執行緒。
程序類比“車間”
2.執行緒:
執行緒是程序中的一個執行單元
執行緒會分配及佔用程序的磁碟,CPU的使用權等。
一個程序可以包含多個執行緒,這就是多執行緒,執行緒也支援併發性。
執行緒類比“車間工人”, 主執行緒類比“車間主任”。
二、多執行緒的好處:
為了保證程式的併發性,提高程式的執行效率,可以讓程序有更多機會得到CPU。
建立一個執行緒的開銷比一個程序的開銷小的多。
多執行緒可以解決很多業務模型
大型高併發技術的核心技術
三、多執行緒的弊端:
若無限制的開闢多條執行緒,勢必會在成程式卡頓,維護困難
引發執行緒安全的問題。

序列:一個一個的去完成的事情。
並行: 指兩個或多個事件在"同一時刻"發生。(同時執行)。
併發:指兩個或多個事件在"同一時間段內發生"(交替執行)。

主執行緒:
程式執行時,至少會有一條主執行緒。
程式正常時,至少會一條主執行緒,會按照程式的執行順序依次執行。
程式出現執行期異常:除了主執行緒,還會有一條執行緒來控制異常資訊的輸出。此時,發生了多執行緒執行情況,搶佔“時間片”的使用權,執行出現的結果產生了隨機性。

Thread 執行緒類:
建立執行緒的幾種方式:
建立執行緒的方式一 --- 繼承extends Thread
自定義一個類繼承 Thread 類
重寫run方法,run() 定義的就是執行緒需要執行的任務。
建立執行緒物件
真正啟動執行緒,呼叫執行緒的start(),底層其實是給CPU註冊當前執行緒,並觸發run()方法執行。進入可執行狀態,開始爭搶時間片,一旦爭搶到時間片,就會進入到執行狀態,執行run()中的任務。
優點:程式碼很簡潔
缺點:Java單繼承,一旦繼承了一個類,想要再拓展再繼承別的類是不被允許的。
建立執行緒的方式二 -- 實現 implements Runnable 介面
自定義一個類實現Runnable介面
重寫run()方法,run()定義執行的任務
建立一個任務物件Runnable
建立一個執行緒物件,將任務物件包裝進去。
啟動執行緒物件。start()

優點:
拓展性好,實現Runnable 介面,還可以再實現介面,再繼承類
同一個執行緒任務物件可以被包裝成多個執行緒物件。
適合多個執行緒去共享同一個資源
實現解耦操作,執行緒任務程式碼可以被多個執行緒共享,執行緒任務程式碼和執行緒獨立。
執行緒池可以放入實現Runnable和Callable執行緒任務物件。
缺點:
相對於繼承Thread 麻煩

建立執行緒的方式三 --- 實現 implements Callable 介面
java.util.concurrent 簡稱 JUC
Callable 提供一個方法 V call() throws Exception
返回一個結果,此時結果可能是執行任務返回的值,也有可能是丟擲的異常

執行步驟:
定義一個類實現Callable介面
重寫Callable方法,返回值與泛型一致,定義執行的任務
建立Callable 帶有返回的任務物件
建立FutureTask (間接實現Runnable介面)任務物件,包裝進Callable 物件,返回Runnable 物件
建立Thread 物件,放入任務物件
啟動執行緒 start()
獲得當前執行緒執行的結果
優點:
拓展性良好,實現Runnable介面,還可以再實現介面,再繼承類
同一個執行緒任務物件可以被包裝成多個執行緒物件
適合多個執行緒去共享一個資源
實現解耦操作,執行緒任務程式碼可以被多個執行緒共享,執行緒任務程式碼和執行緒獨立
執行緒池可以放入實現Runnable和Callable 執行緒任務物件
Callable中的call() 是帶有返回值的任務方法
缺點:
步驟過於繁瑣。
執行緒的方法:
sleep() 或 yield()
static void sleep(long millis) 當前正在執行的執行緒休眠(暫時執行)為指定的毫秒數,根據精度和系統定時器和排程的準確性。
靜態方法 Thread.sleep(1000) ;
呼叫sleep(),執行緒會進入到阻塞狀態 “真的退讓”

         sleep()                    睡眠時間結束

執行狀態 ----------------> 阻塞狀態 ----------------> 可執行狀態(繼續爭搶時間片)

static void yield() 給排程程式一個提示,當前執行緒願意得到它當前的處理器的使用。
靜態方法 Thread.yield();
將當前執行緒的時間片讓度給優先順序高的執行緒,會進入到可執行狀態,擁有再次爭搶時間片的機會 “假意退讓”。
yield()
執行狀態 -----------------> 可執行狀態(繼續爭搶時間片)

void join() 等待該執行緒死亡
非靜態方法 t.join();
呼叫join(),執行緒會進入到阻塞狀態,直到等待執行緒結束。
join() 等待執行緒結束
執行狀態 -------------------> 阻塞狀態 --------------------------------> 可執行狀態(繼續爭搶時間片)

Priority 優先順序:
優先順序:1~10
MAX_PRIORITY 10
MIN_PRIORITY 1
NORM_PRIORITY 5 預設優先順序
結論:優先順序越高,搶佔到的時間片的概率越高,但是是否真的去執行,還是要看是否搶佔了時間片。

Thread.currentThread() 靜態方法,獲得當前正在執行的執行緒的狀態資訊
Thread[Thread - 0 ,10,mian]
Thread[執行緒的名稱,優先順序,執行緒組的名稱]
第一個值:執行緒的名稱,若沒有指定名字,則用預設的Thread-阿拉伯數字
第二個值:優先順序
第三個值:當前執行緒所在的執行緒組,例如main執行緒組

Strat()
真正啟動執行緒的方法
group.add(this);將當前執行緒物件新增到執行緒組中。

執行緒安全:
執行緒同步:解決執行緒安全的問題,當多條執行緒,訪問共享資源,為了讓資源不發生爭搶的現象,能夠有序的進行
第一種方式:同步程式碼塊
synchronized 關鍵字可用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
synchronized(物件鎖){
//會引發執行緒安全的邏輯程式碼塊
}

物件鎖:
引用資料型別 陣列byte[0] 字串“A” this 類名.class
多個執行緒物件 要使用同一把鎖
在任何時候,最多允許一個執行緒擁有同步鎖,誰拿到鎖就進入程式碼塊,其他的執行緒只能在外等著

當多條執行緒訪問共同資源時,發生了“爭搶”現象,若要解決此現象,加上“同步鎖”,產生“互斥效果”
物件鎖是 this who 執行此方法,who就是物件鎖
若此時是同一個物件執行方法,則會發生“等待”現象
若此時不是同一個物件執行方法,則會發生“爭搶”現象
物件鎖是 “A”
若此時是同一個物件執行方法,則會發生“等待”現象
若此時不是同一個物件執行方法,則會發生“等待”現象
3.物件鎖是 開銷最小 byte [] bytes = new byte[0];
若此時是同一個物件執行方法,則會發生“等待”現象
若此時不是同一個物件執行方法,則會發生“爭搶”現象
4.物件鎖是 類名.class 類型別
若此時是同一個物件執行方法,則會發生“等待”現象
若此時不是同一個物件執行方法,則會發生“等待”現象
第二種方式:同步方法
同步方法 synchronized
public synchronized void 方法名(){
//會引發執行緒安全的邏輯程式碼塊
}
物件鎖:
synchronized 修飾非靜態方法,此時物件鎖是 this
若此時是同一個物件執行方法,則會發生“等待”現象
若此時不是同一個物件執行方法,則會發生“爭搶”現象
2.synchronized 修飾靜態方法,此時物件鎖 類名.class
此時無論建立同一個物件,還是不同物件,都會發生“等待”現象

第三種方式:LOCK鎖
Lock鎖 也稱同步鎖
1.java.util.concurrent.locks.Lock
2.public void lock():加同步鎖
public void unlock():釋放同步鎖
3.注意,無論程式碼執行是否出現異常,最終都要釋放同步鎖,finally完成釋放同步鎖。

執行緒狀態:

NEW (尚未啟動的執行緒的執行緒狀態)
RUNNABLE(一個可執行的執行緒的執行緒狀態)
BLOCKED(受阻塞並等待某個監視器鎖的執行緒處於這種狀態)
WAITING(無限期地等待另一個執行緒來執行某一特定操作的執行緒處於這種狀態)
TIMED_WAITTING(等待另一個執行緒來執行取決於指定等待時間的操作的執行緒處於這種狀態)
TERMINATED(已退出的執行緒處於這種狀態)

NEW 代表是新建狀態,是尚未啟動的執行緒狀態,現在new Thread 執行緒,start()真正啟動執行緒的方法,若有CPU的使用權,會進入到RUNNABLE狀態,即可執行狀態,反之,會進入BLOCKED阻塞狀態,進入執行狀態時,若時間片用完就會進入阻塞狀態,若阻塞狀態得到時間片,則會進入執行狀態。執行狀態時呼叫 Object wait() 時會進入無限等待狀態(WAITING),此時,若呼叫 Object notify()或Object notifyAll() ,且獲得物件鎖,就會從無限等待狀態變成執行狀態。若在無限等待狀態,呼叫Object notify()未獲得物件鎖,則會進入阻塞狀態。 執行狀態,呼叫 Thread sleep(long)或 Object wait(long)時,則會進入計時等待狀態(TIMED_WAITING),若sleep()時間結束,wait時間結束,獲得物件鎖,notify喚醒,獲得物件鎖,進入執行狀態。若當前是計時等待狀態,且wait時間結束,未獲得物件鎖,wait提前被喚醒,未獲得物件鎖,則會進入阻塞狀態。死亡狀態是在執行狀態時 run()執行結束或出現異常時轉換。

wait() 和 sleep() 兩者的區別:
Object 類中的wait() 非靜態的方法,屬於物件的
Thread 類中的sleep() 靜態方法,屬於當前執行緒的
wait() 釋放物件鎖,sleep() 不會釋放物件鎖
sleep() 是可以不使用synchronized 關鍵字
wait()必須使用synchronized 關鍵字的,否則出現java.lang.IllegalMonitorStateException 異常
4.sleep(long time) 不需要被喚醒,時間結束了自動醒。
wait(long time ) 時間結束了自動醒;但是wait()一直等待,直到被喚醒。

執行緒池:
1.單執行緒的執行緒池:
newSingleThreadExecutor
建立一個單執行緒的執行緒池。這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務。
如果這個唯一的執行緒因異常結束,那麼會有一個新的執行緒來替代它
此執行緒保證所有任務的執行順序按照任務的提交順序執行。

2.建立一個大小無限的執行緒池:
建立一個大小無限的執行緒池,此執行緒池支援定時以及週期性執行任務的需求。
newScheduleThreadPool
第一個引數 :Runnable 定期執行任務
第二個引數:initialDelay 首次執行任務的延遲時間
第三個引數:period 執行任務的週期
第四個引數:unit 前兩個時間的單位。

3.建立大小固定的執行緒池:
newFixedThreadPool
建立固定大小的執行緒池
每次提交一個任務就建立一個執行緒,直到執行緒達到執行緒池的最大大小。
執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因執行異常而結束,那麼執行緒池會補充一個新的執行緒。

4.建立一個可快取的執行緒池:
newCachedThreadPool
建立一個可快取的執行緒池
如果執行緒池的大小超過了處理任務所需要的執行緒,那麼會回收部分空閒(60秒不執行任務)
的執行緒,當任務數增加時,此執行緒池又可以智慧的新增新執行緒來處理任務。此執行緒池不會對執行緒池的大小做出限制
執行緒池的大小完全依賴於作業系統(或者說JVM)能夠建立的最大執行緒大小。