Java多線程之內置鎖與顯式鎖

Java中具有通過Synchronized實現(xiàn)的內置鎖笆豁,和ReentrantLock實現(xiàn)的顯示鎖郎汪,這兩種鎖各有各的好處,算是互有補充闯狱,今天就來做一個總結煞赢。

Synchronized

內置鎖獲得鎖和釋放鎖是隱式的,進入synchronized修飾的代碼就獲得鎖哄孤,走出相應的代碼就釋放鎖照筑。

synchronized(list){ //獲得鎖
    list.append();
    list.count();
}//釋放鎖

通信

與Synchronized配套使用的通信方法通常有wait(),notify()。

wait()方法會立即釋放當前鎖瘦陈,并進入等待狀態(tài)凝危,等待到相應的notify并重新獲得鎖過后才能繼續(xù)執(zhí)行;notify()不會立刻立刻釋放鎖晨逝,必須要等notify()所在線程執(zhí)行完synchronized塊中的所有代碼才會釋放蛾默。用如下代碼來進行驗證:

public static void main(String[] args){
    List list = new LinkedList();
    Thread r = new Thread(new ReadList(list));
    Thread w = new Thread(new WriteList(list));
    r.start();
    w.start();
}
class ReadList implements Runnable{

    private List list;

    public ReadList(List list){ this.list = list; }

    @Override
    public void run(){
        System.out.println("ReadList begin at "+System.currentTimeMillis());
        synchronized (list){
            try {
                Thread.sleep(1000);
                System.out.println("list.wait() begin at "+System.currentTimeMillis());
                list.wait();
                System.out.println("list.wait() end at "+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("ReadList end at "+System.currentTimeMillis());

    }
}

class WriteList implements Runnable{

    private List list;

    public WriteList(List list){ this.list = list; }

    @Override
    public void run(){
        System.out.println("WriteList begin at "+System.currentTimeMillis());
        synchronized (list){
            System.out.println("get lock at "+System.currentTimeMillis());
            list.notify();
            System.out.println("list.notify() at "+System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("get out of block at "+System.currentTimeMillis());
        }
        System.out.println("WriteList end at "+System.currentTimeMillis());

    }
}

運行結果

ReadList begin at 1493650526582
WriteList begin at 1493650526582
list.wait() begin at 1493650527584
get lock at 1493650527584
list.notify() at 1493650527584
get out of block at 1493650529584
WriteList end at 1493650529584
list.wait() end at 1493650529584
ReadList end at 1493650529584

可見讀線程開始運行,開始wait過后捉貌,寫線程才獲得鎖支鸡;寫線程走出同步塊而不是notify過后冬念,讀線程才wait結束,亦即獲得鎖牧挣。所以notify不會釋放鎖急前,wait會釋放鎖。
值得一提的是瀑构,notifyall()會通知等待隊列中的所有線程裆针。

編碼

編碼模式比較簡單,單一检碗,不必顯示的獲得鎖据块,釋放鎖,能降低因粗心忘記釋放鎖的錯誤折剃。使用模式如下:

synchronized(object){ 
    
}

靈活性

  • 內置鎖在進入同步塊時另假,采取的是無限等待的策略,一旦開始等待怕犁,就既不能中斷也不能取消边篮,容易產生饑餓與死鎖的問題
  • 在線程調用notify方法時,會隨機選擇相應對象的等待隊列的一個線程將其喚醒奏甫,而不是按照FIFO的方式戈轿,如果有強烈的公平性要求,比如FIFO就無法滿足

性能

Synchronized在JDK1.5及之前性能(主要指吞吐率)比較差阵子,擴展性也不如ReentrantLock思杯。但是JDK1.6以后,修改了管理內置鎖的算法挠进,使得Synchronized和標準的ReentrantLock性能差別不大色乾。

ReentrantLock

ReentrantLock是顯示鎖,需要顯示進行 lock 以及 unlock 操作领突。

通信

與ReentrantLock搭配的通行方式是Condition暖璧,如下:

private Lock lock = new ReentrantLock();  
private Condition condition = lock.newCondition(); 
condition.await();//this.wait();  
condition.signal();//this.notify();  
condition.signalAll();//this.notifyAll();  

Condition是被綁定到Lock上的,必須使用lock.newCondition()才能創(chuàng)建一個Condition君旦。從上面的代碼可以看出澎办,Synchronized能實現(xiàn)的通信方式,Condition都可以實現(xiàn)金砍,功能類似的代碼寫在同一行中局蚀。
而Condition的優(yōu)秀之處在于它可以為多個線程間建立不同的Condition,比如對象的讀/寫Condition恕稠,隊列的空/滿Condition琅绅,在JDK源碼中的ArrayBlockingQueue中就使用了這個特性:

 public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    notEmpty.signal();
}
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    notFull.signal();
    return x;
}

編碼

Lock lock = new ReentrantLock();
lock.lock();
try{
    
}finally{
    lock.unlock();
}

相比于Synchronized要復雜一些,而且一定要記得在finally中釋放鎖而不是其他地方谱俭,這樣才能保證即使出了異常也能釋放鎖奉件。

靈活性

  • lock.lockInterruptibly() 可以使得線程在等待鎖是支持響應中斷;lock.tryLock() 可以使得線程在等待一段時間過后如果還未獲得鎖就停止等待而非一直等待昆著。有了這兩種機制就可以更好的制定獲得鎖的重試機制县貌,而非盲目一直等待,可以更好的避免饑餓和死鎖問題
  • ReentrantLock可以成為公平鎖(非默認的)凑懂,所謂公平鎖就是鎖的等待隊列的FIFO煤痕,不過公平鎖會帶來性能消耗,如果不是必須的不建議使用接谨。這和CPU對指令進行重排序的理由是相似的摆碉,如果強行的按照代碼的書寫順序來執(zhí)行指令,就會浪費許多時鐘周期脓豪,達不到最大利用率

性能

雖然Synchronized和標準的ReentrantLock性能差別不大巷帝,但是ReentrantLock還提供了一種非互斥的讀寫鎖,
也就是不強制每次最多只有一個線程能持有鎖扫夜,它會避免“讀/寫”沖突楞泼,“寫/寫”沖突,但是不會排除“讀/讀”沖突笤闯,
因為“讀/讀”并不影響數據的完整性堕阔,所以可以多個讀線程同時持有鎖,這樣在讀寫比較高的情況下颗味,性能會有很大的提升超陆。

下面用兩種鎖分別實現(xiàn)的線程安全的linkedlist:

class RWLockList {//讀寫鎖

    private List list;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();

    public RWLockList(List list){this.list = list;}

    public int get(int k) {
        readLock.lock();
        try {
            return (int)list.get(k);
        } finally {
            readLock.unlock();
        }
    }

    public void put(int value) {
        writeLock.lock();
        try {
            list.add(value);
        } finally {
            writeLock.unlock();
        }
    }
}

class SyncList  {

    private List list;

    public SyncList(List list){this.list = list;}

    public synchronized int  get(int k){
        return (int)list.get(k);
    }

    public synchronized void put(int value){
        list.add(value);
    }

}

讀寫鎖測試代碼:

List list = new LinkedList();
for (int i=0;i<10000;i++){
    list.add(i);
}
RWLockList rwLockList = new RWLockList(list);//初始化數據

Thread writer = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i=0;i<10000;i++){
            rwLockList.put(i);
        }
    }
});
Thread reader1 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i=0;i<10000;i++){
            rwLockList.get(i);
        }
    }
});
Thread reader2 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i=0;i<10000;i++){
            rwLockList.get(i);
        }
    }
});
long begin = System.currentTimeMillis();
writer.start();reader1.start();reader2.start();
try {
    writer.join();
    reader1.join();
    reader2.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("RWLockList take "+(System.currentTimeMillis()-begin) + "ms");

同步鎖測試代碼:

List list = new LinkedList();
for (int i=0;i<10000;i++){
    list.add(i);
}
SyncList syncList = new SyncList(list);//初始化數據
Thread writerS = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i=0;i<10000;i++){
            syncList.put(i);
        }
    }
});
Thread reader1S = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i=0;i<10000;i++){
            syncList.get(i);
        }
    }
});
Thread reader2S = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i=0;i<10000;i++){
            syncList.get(i);
        }
    }
});
long begin1 = System.currentTimeMillis();
writerS.start();reader1S.start();reader2S.start();
try {
    writerS.join();
    reader1S.join();
    reader2S.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("SyncList take "+(System.currentTimeMillis()-begin1) + "ms");

結果:
RWLockList take 248ms
RWLockList take 255ms
RWLockList take 249ms
RWLockList take 224ms

SyncList take 351ms
SyncList take 367ms
SyncList take 315ms
SyncList take 323ms

可見讀寫鎖的確是優(yōu)于純碎的互斥鎖

總結

內置鎖最大優(yōu)點是簡潔易用,顯示鎖最大優(yōu)點是功能豐富浦马,所以能用內置鎖就用內置鎖时呀,在內置鎖功能不能滿足之時在考慮顯示鎖。

關于兩種鎖捐韩,目前接觸到的就是這么多退唠,總結不到位之處,歡迎拍磚荤胁。我的主頁 mageek.cn

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末瞧预,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子仅政,更是在濱河造成了極大的恐慌垢油,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圆丹,死亡現(xiàn)場離奇詭異滩愁,居然都是意外死亡,警方通過查閱死者的電腦和手機辫封,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門硝枉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來廉丽,“玉大人,你說我怎么就攤上這事妻味≌梗” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵责球,是天一觀的道長焦履。 經常有香客問我,道長雏逾,這世上最難降的妖魔是什么嘉裤? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮栖博,結果婚禮上屑宠,老公的妹妹穿的比我還像新娘。我一直安慰自己笛匙,他們只是感情好侨把,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妹孙,像睡著了一般秋柄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蠢正,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天骇笔,我揣著相機與錄音,去河邊找鬼嚣崭。 笑死笨触,一個胖子當著我的面吹牛,可吹牛的內容都是我干的雹舀。 我是一名探鬼主播芦劣,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼说榆!你這毒婦竟也來了虚吟?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤签财,失蹤者是張志新(化名)和其女友劉穎串慰,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體唱蒸,經...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡邦鲫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了神汹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庆捺。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡古今,死狀恐怖,靈堂內的尸體忽然破棺而出滔以,到底是詐尸還是另有隱情沧卢,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布醉者,位于F島的核電站,受9級特大地震影響披诗,放射性物質發(fā)生泄漏撬即。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一呈队、第九天 我趴在偏房一處隱蔽的房頂上張望剥槐。 院中可真熱鬧,春花似錦宪摧、人聲如沸粒竖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕊苗。三九已至,卻和暖如春沿彭,著一層夾襖步出監(jiān)牢的瞬間朽砰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工喉刘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞧柔,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓睦裳,卻偏偏與公主長得像造锅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子廉邑,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內容