去年看完的《圖解java多線程設計模式》,可惜當時沒做筆記,導致后來忘了許多東西,打算再溫習下這本書,順便在這里記錄一下~
一、順序執行、並行、並發
順序執行:多個操作按照順序依次執行。
並行:多個任務同時進行,同一時間內可以執行多個任務,這種方式,叫做並行執行,比如多核處理器,多個核可以同時處理多個任務。
並發:多個任務通過切分時間段,來達到“同時進行”的效果,比如單核處理器,在“同時”處理多個任務時,就會不停的切換來執行不同的任務,不可能有同一時間執行不同任務的情況。
下面引用別人的一句話來說明下並行和並發:
並發是兩個任務可以在重疊的時間段內啟動,運行和完成。並行是任務在同一時間運行,例如,在多核處理器上。 並發是獨立執行過程的組合,而並行是同時執行(可能相關的)計算。 並發是一次處理很多事情,並行是同時做很多事情。
應用程序可以是並發的,但不是並行的,這意味着它可以同時處理多個任務,但是沒有兩個任務在同一時刻執行。
應用程序可以是並行的,但不是並發的,這意味着它同時處理多核CPU中的任務的多個子任務。
一個應用程序可以即不是並行的,也不是並發的,這意味着它一次一個地處理所有任務。
應用程序可以即是並行的也是並發的,這意味着它同時在多核CPU中同時處理多個任務。
二、synchronized修飾符
當我們說一個線程獲得鎖以后,則意味着這個線程可以執行當前對象(或類)里的synchronized方法,而且他線程則需要排隊等待該線程釋放鎖以后才可能獲得鎖,進而執行鎖里面的程序。
synchronized修飾后,存在對象鎖和類鎖兩種類型。
2.1:對象鎖
synchronized (this){
...略
}
2.2:類鎖
synchronized (XXX.class){
...略
}
2.3:區別和作用域
對象鎖指的是當前線程獲得了某個實例的鎖,假如有個Word類,有A、B兩個同步方法,C屬於普通方法,如圖所示:

圖1
可以發現,對象鎖的作用域只針對當前對象生效,就像w1和w2里的A方法可以被不同的線程同時執行,但是同一個對象內的同步塊,卻只允許持有當前對象鎖的線程執行,如t2、t3均被擋在了外面,當t1釋放鎖以后,t2、t3才會重新競爭鎖,競爭到鎖以后就會執行自己想要執行的同步邏輯。
類鎖指的是當前線程獲得了某個類的鎖,還是Word類,有A、B兩個static方法(靜態方法屬於類方法,加synchronized修飾符后等效於上面提到的synchronized(Word.class)),C屬於普通static方法,如圖所示:

圖2
跟上面相比較,這里的t5受到了t1的影響,因為t1獲得了Word類的鎖,w1和w2共屬一個類,因此t1獲得類鎖以后,其他線程想要訪問這個類里的同步塊,就得等到t1釋放鎖以后才可以繼續競爭鎖然后執行自己想要執行的同步邏輯。
三、線程間的通信
3.1:Wait
這幾個方法是屬於每個實例對象的,所有實例都擁有一個“等待隊列”(虛擬概念,實例里並不存在該字段),它是在實例的wait方法調用后存放停止操作線程的隊列。執行wait方法后,線程進入當前實例的“等待隊列”,以下幾種情況可以讓線程退出“等待隊列”:
①其他線程調用notify、notifyAll方法來將其喚醒
②其他線程調用interrupt來將其喚醒
③wait方法本身超時
當執行了下面的代碼:
obj.wait();
我們可以說當前線程在obj上發生了等待,當前線程進入了obj的“等待隊列”,此時當前線程會讓出鎖,讓其他線程繼續競爭獲得該實例的鎖(因此這里有個規則,調用wait的線程必須持有當前實例對象的鎖)
過程如下圖:

圖3
3.2:notify
現在先來介紹下notify,該方法會將等待隊列里的線程取出,讓其退出等待並參與鎖競爭然后繼續執行上次wait后沒有執行完的語句。整體過程如下圖所示:

圖4
可以看到,t1在被掛起后,會因為t2調用了同實例的notify方法,而讓t1被從等待隊列里釋放,重新加入到所得競爭力,t2執行完畢后釋放鎖,鎖又再次被t1競爭到,t1將繼續執行上次被掛起時后面未執行完的語句。
需要指出的是,如果等待隊列里的線程是多個,那么被喚醒的那一個,將會是等待隊列里所有線程隨機的一個,不會特定哪一個線程會被喚起。
3.3:notifyAll
接下來介紹notifyAll方法,顧名思義,就是將等待隊列里的線程全部喚起,然后這些線程將全部加入到鎖競爭,競爭到,繼續完成上次被掛起時未執行完畢的操作,流程圖如下:

圖5
說明,當線程調用實例的wait、notify、notifyAll方法有個大前提,就是必須要求該線程擁有該實例的鎖,否則會拋IllegalMonitorStateException異常。
在編寫程序時,是該選擇notify還是選擇notifyAll?這個可以指出的是,notifyAll往往更加健壯,而notify由於喚起的線程少,因此效率會更高,但是存在程序停止的風險。
附上使用wait、notify進行線程通信的例子:
