Java高併發理論篇
作為IT程式猿,掌握多執行緒是作為伺服器開發人員不可缺少的技能之一,同時在單核CPU的情況下,為了模擬多核的情況,我們也必須掌握多執行緒程式設計的問題,下來我們瞭解一下並行程式設計:
一:前提知識的相關概念
同步、非同步:
同步非同步通常形容方法呼叫,
同步:在方法呼叫中,同步方法指的是方法的執行必須有序進行,當前方法的執行必須在上一個方法的結束;即有序執行
非同步:在方法呼叫中,方法的執行在另一個執行緒中真實地執行,當前的呼叫執行緒可以處理別的事情;即更像是一個命令實施者,具體的工作由別的執行緒完成,
併發、並行:
併發:多個任務輪流爭取CPU的資源,多個任務交替執行,看起來好像多個程式一起執行;
其實還是多個任務在共享一個CPU;
並行:每個執行緒使用一個CPU,真正的一起執行;
臨界區:
表示一個公共資源或者共享資料,可以被多個執行緒使用;每一次,只能有一個執行緒使用它,一旦臨界區資源被專用(加鎖),其他執行緒想要使用這個資源,就必須等待;
阻塞、非阻塞:
阻塞:當一個執行緒佔用了臨界資源,那麼當其他執行緒想要訪問臨界資源的時候,就必須等待,等待會導致執行緒掛起,即將當前執行緒放入到等待佇列中,直到被喚醒;
非阻塞:執行緒之間的執行不受共享資源的狀態影響,即不會被掛起;
執行緒之間的鎖機制引發的問題->死鎖、活鎖、飢餓:
死鎖:執行緒之間互相佔據物件需要的資源,又不肯釋放資源,一直處理迴圈等待的過程,導致一直僵持的過程;例如A方法執行期間有臨界資源 A1, B1;B方法執行期間有臨界資源 B1,A1;而A方法需要使用B1資源後,才可以釋放釋放A1,的鎖,而B方法需要使用A1的資源後,才可以釋放B1的鎖,因此造成 A 等待 B1, B等待A1,造成執行緒的一直等待;
飢餓:執行緒因為某種原因(例如執行緒的優先順序比較低)一直等待資源,無法獲取所需要的資源,導致一直無法執行;
活鎖:兩個執行緒互相主動將資源釋放,讓給對方,但沒有一個執行緒可以同時拿到所有資源而正常執行;
程序,執行緒:
程序是系統進行資源分配和排程的基本單位,程序是基本執行的實體,是執行緒的容器,執行一個程式,就相當於執行一個程序。當我們程式有很多小的任務需要執行的時候,我們使用程序進行任務之間的切換,比較費時,且資料交換麻煩,因此引入執行緒;
執行緒:是程式執行的最小單位,執行緒之間可共享資料,執行緒之間切換的成本較低,一個程序包含很多執行緒;
多執行緒的一些性質:
原子性:Atomicity
原子性指的是一個操作不可能被中斷,即如果多個執行緒一起執行的時候,一個操作一旦開始,就不會被其他執行緒干擾;
對於32位作業系統,long型資料的讀寫不是原子性的。
可見性:Visibility
可見性是指當一個執行緒修改了某一個共享變數的值,其他執行緒是否能夠立即知道這個修改;對於序列來說,不存在這個問題,此問題只針對於並行;
有序性:Ordering
當程式執行時,有可能會進行指令重拍,重拍後的指令和原指令的順序未必一致;
執行緒的狀態:
建立New,啟動Runnable,阻塞Blocked,停止Waiting,結束Terminated,有限等待Time_Waiting;
New新建狀態表示剛剛創立的簡稱,這個執行緒還沒有開始執行,需要等到start()方法呼叫時,才表示執行緒執行;
Runnable執行狀態表示執行緒所需的一些資源都已經準備好了,可以執行
Blocked阻塞狀態,表示當遇到Synchronized同步塊是,就會進行blocked阻塞狀態,這個執行緒會暫停執行,直到獲取請求的鎖
Waiting/Time Waiting:Waiting等待狀態會進入一個無時間限制的等待,Time_Waiting會進行一個有時間的等待;
Terminated結束:當執行緒執行完畢,就會進行結束階段;
執行緒的方法:
終止執行緒stop():
Thread提供了一個stop()方法,stop()方法會立即終止一個執行緒,並且會立即釋放這個執行緒所持有的鎖,而鎖機制是用來維持物件的一致性,所以直接釋放鎖會導致資料有可能不一致,stop在eclipse中被標記為廢棄的方法,所以儘量不要使用stop()方法來停止執行緒;
執行緒中斷Interrupt():
中斷就是讓目標執行緒停止執行,執行緒中斷並不會使執行緒立即退出,而給執行緒傳送一個通知,告知目標執行緒,Thread.interrupt()方法,會通知目標執行緒中斷,設定中斷標誌位,中斷標誌位表示當前執行緒已經被中斷,Thread.isInterupted()用來判斷執行緒是否中斷,Thread.interrupted()方法用來判斷當前執行緒的中斷狀態,但同時會清除當前執行緒的中斷標誌位狀態;
睡眠sleep():
Thread.sleep()方法會讓當前執行緒休眠若干時間,他會丟擲一個InterruptedException中斷異常,
等待方法wait()和notify():
這兩個方法不在Thread類中,而是輸出Object類,即執行緒A中,呼叫obj.wait()方法,那麼執行緒A就會停止繼續執行,轉為等待狀態,直到obj.notify()方法為止;當一個執行緒呼叫Object.wait(),那執行緒會進入object物件的等待佇列,當呼叫object.notify()被呼叫時,它就會從這個等待佇列中,隨機選擇一個執行緒,並將其喚醒。Object.wait()方法並不能隨便呼叫,它必須包含在對應的synchronized語句中,無論wait還是notify都需要首先獲得目標物件的一個監視器(鎖),wait()方法在執行後,會釋放這個監視器;Object物件的notifyAll()被呼叫的時候,它會喚醒這個等待佇列中所有等待的執行緒,而不是隨機選擇一個;
掛起suspend():
執行緒掛起suspend和繼續執行resume,是一對相反的操作,被掛起的執行緒必須要等到resume()後,才能繼續執行;
suspend()在導致執行緒暫停的同時,並不會去釋放任何鎖的資源,即它還持有物件的鎖,因此,其他任何執行緒想要訪問鎖的資源時,都會被阻塞,導致無法繼續執行,直到對應的執行緒進行了resume()操作,被掛起的操作才會繼續執行;
等待執行緒結束join()、謙讓yield():
執行緒呼叫join:即這個執行緒需要等待依賴執行緒執行完畢,才能繼續執行,join的本質是讓呼叫執行緒wait()在當前執行緒物件例項上,
Thread.yield():靜態方法,它會使當前執行緒讓出CPU,但是並不代表當前執行緒不在執行了,而是讓出CPU後,執行緒還會進行CPU資源的爭奪,至於能不能再次獲得CPU的資源,視情況而定。
執行緒的優先順序:
執行緒的優先順序代表執行緒可以獲取資源的機率,優先順序越高,代表越有可能獲取資源,但是並不是一定的,執行緒的優先順序內建了三個靜態標量表示:
public final static int MIN_PRIORITY =1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
執行緒的優先順序的有效範圍為1-10;
執行緒安全和Synchronized:
當多個執行緒同時訪問一個共享變數的時候,會發生衝突問題,所以需要在呼叫之間合理的安排他們的訪問順序,使用synchronized關鍵字,可以對共享資源加鎖,使執行緒之間有序的訪問共享資源;
synchronized關鍵字應用的地方:
指定加鎖對物件,給物件加鎖,進入同步程式碼前要獲得給定物件的鎖;
直接作用於例項方法,相當於給當前例項加鎖,進入同步程式碼之前要獲得當前例項的鎖;
直接作用於靜態方法,相當於給當前類加鎖,進入同步程式碼之前要獲得當前類的鎖;
當使用synchronized關鍵字修飾的時候,修飾物件會有一個鎖,同時也會有一個等待佇列/阻塞佇列,當執行緒訪問共享資源的時候,執行緒必須獲取修飾物件的鎖,才可以訪問或使用這個資源,直到執行緒執行完畢,才會釋放鎖,在這期間,如果有其他執行緒想要訪問共享資源,因為鎖被佔用,所以會被阻塞,放入到等待佇列中,當鎖被釋放,會隨機從等待佇列中挑選一個資源,來獲取鎖,並使用共享資源。
注意:不同物件,例項都有自己的鎖,在建立執行緒的時候,必須讓執行緒都指向同一個Thread子類或Runnable介面例項,這樣才能保證多個執行緒在工作時,對唯一的共享資源使用同一個鎖,從而保證執行緒安全,如果在建立執行緒的時候,使用不同的物件,那麼執行緒之間使用的鎖就不會是同一個鎖,因為每個物件,例項都有自己的鎖,所以有可能造成資料不一致.
參考《Java 高併發程式設計》 --葛一鳴,郭超