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。