標籤:java 多線程
java多線程中提供的鎖:synchronized和lock。
(一)synchronized
1、synchronized的使用
每個對象都內建鎖,鎖可以同步執行個體方法(this是對象鎖)、靜態方法(class是對象鎖)、方法塊(synchronized參數是對象鎖)
下面是鎖住執行個體方法:
public synchronized void add(){ a++; }使用注意點:
(1)Object的wait、notify和notifyAll使用時需在代碼外層加鎖,等待和喚醒鎖必須相同,使用的鎖不能發生改變,不然會拋出IllegalMonitorStateException異常
/** * 線程等待喚醒測試 * * @author peter_wang * @create-time 2014-10-9 下午2:50:36 */public class ThreadNotifyTest { private static Integer num = new Integer(0); /** * @param args */ public static void main(String[] args) { final Thread thead1 = new Thread() { @Override public void run() { synchronized (num) { try { sleep(2000); num.wait(); System.out.println("解鎖成功"); } catch (InterruptedException e) { e.printStackTrace(); } } } }; thead1.start(); Thread thead2 = new Thread() { @Override public void run() { try { sleep(1000);// num = new Integer(1); //A num++;//B } catch (InterruptedException e) { e.printStackTrace(); } } }; thead2.start(); }}無論執行A或B,改變了鎖num後,wait執行的時候會拋出IllegalMonitorStateException異常。
對wait、notify加鎖是為了保證它們在執行中的原子性。
(2)使用的鎖盡量是不可變對象,使用private final Object對象,可變化的對象可能導致不可預知的後果,如wait的問題。
(3)synchronized鎖住地區盡量減少,提高效能。
2、synchronized原理探究
(1)線程狀態
當多個線程同時請求某個對象監視器時,對象監視器會設定幾種狀態用來區分請求的線程:
Contention List:所有請求鎖的線程將被首先放置到該競爭隊列
Entry List:Contention List中那些有資格成為候選人的線程被移到Entry List,降低對Contention List的爭用
Wait Set:那些調用wait方法被阻塞的線程被放置到Wait Set
OnDeck:任何時刻最多隻能有一個線程正在競爭鎖,該線程稱為OnDeck Owner:獲得鎖的線程稱為Owner !Owner:釋放鎖的線程
(2)鎖類型
公平鎖和非公平鎖
公平鎖:每個線程取得調度的幾率是一樣的
非公平鎖:每個線程取得的調度幾率不同,是公平鎖吞吐率的5-10倍
自旋鎖和阻塞鎖
自旋鎖:線程阻塞調度過程設計到操作linux核心線程,需要在使用者態和核心態之間切換狀態,效能消耗比較大,自旋機制讓請求調度的線程內部自迴圈,不切換狀態等待一段時間,若仍然未能擷取調度機會再轉換鎖類型
阻塞鎖:阻塞鎖線上程競爭時,無擷取到調度的線程直接進入阻塞隊列
多種阻塞鎖類型
偏向鎖:在大多數情況下,鎖都存在於單線程中,為了讓線程獲得鎖減少效能代價,同一線程多次重入,不會執行CAS操作,直到遇見多線程競爭,轉換成其他類型
輕量鎖:偏向鎖的升級版或者直接設定系統不使用偏向鎖直接進入輕量鎖,比偏向鎖多了步CAS操作,當前若鎖未被其他線程鎖住即可使用,操作失敗進入自旋鎖
重量鎖:完整的阻塞鎖狀態,對象監視器(Monitor),由自旋鎖逾時升級而成
鎖的進化過程:偏向鎖—>輕量鎖—>自旋鎖—>重量鎖
(3)總結
synchronized執行時,優先使用偏向鎖或輕量鎖提升效能,碰到多線程鎖住現象,進入自旋狀態,等待未果進入重量鎖階段,阻塞線程,放入阻塞隊列,切換線程狀態。
(二)Lock
1、ReentrantLock的使用
private ReentrantLock mlock = new ReentrantLock();@Override public void write() { mlock.lock(); try { long startTime = System.currentTimeMillis(); System.out.println("開始往這個buff寫入資料…"); for (;;)// 類比要處理很長時間 { if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) break; } System.out.println("終於寫完了"); } finally { mlock.unlock(); } }ReentrantLock在lock的時候鎖住執行個體對象,必須在finally中unlock解鎖,防止異常拋出未解鎖。
2、ReentrantLock原理分析
ReentrantLock中的操作都是基於Sync,Sync繼承自AbstractQueuedSynchronizer。
AbstractQueuedSynchronizer通過構造一個基於阻塞的CLH隊列容納所有的阻塞線程,而對該隊列的操作均通過Lock-Free(CAS)操作,但對已經獲得鎖的線程而言,ReentrantLock實現了偏向鎖的功能。
(三)synchronized和ReentrantLock對比
1、效能上synchronized是native方法效能最佳化較多,ReentrantLock是java層實現效能不一定很好。
2、ReentrantLock提供了更多功能,如公平鎖和非公平鎖設定等,但是需要使用finally解鎖。
3、在普通情況下使用synchronized,在業務複雜需要使用ReentrantLock特殊功能的才使用ReentrantLock。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
java-多線程深入(六)鎖