天天看點

Java的一堆鎖是幹嘛的?

Java中的鎖有很多種,經常會聽到“鎖”這個詞。

猶如每天出門時,????就是一種“鎖”,拿不到????,就進不去了。

Java那麼多種類的鎖,都是按不同标準來分類的。就像商店裡的各種商品,可以按式樣,也可以按顔色或者尺寸。

其實它們都是一種思想。

為什麼會有鎖?

一個程序可以包含多個線程,那麼多個線程就會有競争資源的問題出現,為了互相不打架,就有了鎖的概念了。 一個線程也可以自己完成任務,但就像一個小組可以互相配合、共同完成任務,比一個人要快很多是不是?

分類

來個圖~

Java的一堆鎖是幹嘛的?

1 悲觀 Vs 樂觀

林妹妹比較悲觀,寶玉比較樂觀~
Java的一堆鎖是幹嘛的?

1.1 悲觀鎖

看名字便知,它是悲觀的,總是想到最壞的情況。 鎖也會悲觀,它并不是難過,它隻是很謹慎,怕做錯。

每次要讀data的時候,總是覺得其他人會修改資料,是以先加個????,讓其他人不能改資料,再慢慢讀~

要是你在寫一篇日記,怕别人會偷看了,就加了個打開密碼,别人必須拿到密碼才能打開這篇文章。這就是悲觀鎖了。

應用: synchronized關鍵字和Lock的實作類都是悲觀鎖。

1.2 樂觀鎖

它很樂觀,總是想着最好的情況。 它比較大條,不會太擔心。如果要發生,總會發生,如果不會發生,那就不會。為什麼要擔心那麼多?

每次讀data時,總是樂觀地想沒有其他人會同時修改資料,不用加鎖,放心地讀data。 但在更新的時候會判斷一下在此期間别人有沒有去更新這個資料。

就像和别人共同編輯一篇文章,你在編輯的時候别人也可以編輯,而且你覺得别人不會改動到你寫的部分,那就是樂觀鎖了。

事事無絕對,悲觀也好樂觀也好,沒有絕對的悲觀,也沒有絕對的樂觀。隻是在這個當時,相信,還是不相信。

悲觀 & 樂觀 比較

類型 實作 使用場景 缺點
悲觀鎖 synchronized關鍵字和Lock的實作類 适合寫操作多的場景,可以保證寫操作時資料正确 如果該事務執行時間很長,影響系統的吞吐量
樂觀鎖 無鎖程式設計,CAS算法 适合讀操作多的場景,能夠大幅提升其讀操作的性能 如果有外來事務插入,那麼就可能發生錯誤

應用

樂觀鎖 —— CAS(Compare and Swap 比較并交換)

是樂觀鎖的一種實作方式。

簡單來說,有3個三個操作數:

  • 需要讀寫的記憶體值 V。
  • 進行比較的值 A。
  • 要寫入的新值 B。

2 公平 Vs 非公平

沒有絕對的公平,也沒有絕對的不公平。

公平,就是按順序排隊嘛。 公平鎖維護了一個隊列。要擷取鎖的線程來了都排隊。

Java的一堆鎖是幹嘛的?

非公平,上來就想搶到鎖,好像一個不講道理的,搶不到的話,隻好再去乖乖排隊了。 非公平鎖沒有維護隊列的開銷,沒有上下文切換的開銷,可能導緻不公平,但是性能比fair的好很多。看這個性能是對誰有利了。

舉個栗子

Java的一堆鎖是幹嘛的?
Java的一堆鎖是幹嘛的?

3 可重入鎖 Vs 非可重入鎖

廣義上的可重入鎖,而不是單指JAVA下的ReentrantLock。

可重入鎖,也叫做遞歸鎖,指的是同一線程外層函數獲得鎖之後,内層遞歸函數仍然有擷取該鎖的代碼,但不受影響。

這句話神馬意思?

這種鎖是可以反複進入的。

當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。

class MyClass {
   public synchronized void method1() {
       enterNextRoom();
  }

   public synchronized void method2() {
       // todo
  }
}      

兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A擷取了這個對象的鎖,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因為線程A已經持有了該對象的鎖,而又在申請擷取該對象的鎖,這樣就會線程A一直等待永遠不會擷取到的鎖。

如果不是可重入鎖的話,method2可能不會被目前線程執行,可能造成死鎖。

可重入鎖最大的作用是避免死鎖。

實作:

  • synchronized
  • ReentrantLock

ReentrantLock構造函數

提供了是否公平鎖的初始化:

/**
    * Creates an instance of {@code ReentrantLock}.
    * This is equivalent to using {@code ReentrantLock(false)}.
    */
   public ReentrantLock() {
       sync = new NonfairSync();
  }

   /**
    * Creates an instance of {@code ReentrantLock} with the
    * given fairness policy.
    *
    * @param fair {@code true} if this lock should use a fair ordering policy
    */
   public ReentrantLock(boolean fair) {
       sync = fair ? new FairSync() : new NonfairSync();
  }      

使用ReentrantLock必須在finally控制塊中進行解鎖操作。

在資源競争不激烈的情形下,性能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的性能一下子能下降好幾十倍,而ReentrantLock确還能維持常态。

高并發量情況下使用ReentrantLock。

優點: 可一定程度避免死鎖。

  • Semaphore
  • AtomicInteger、AtomicLong等
不可重入鎖的栗子

用自旋鎖來模拟,代碼如下:

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {
   Thread current = Thread.currentThread();
   //這句是很經典的“自旋”文法,AtomicInteger中也有
   for (;;) {
       if (!owner.compareAndSet(null, current)) {
           return;
      }
  }
}

   public void unlock() {
       Thread current = Thread.currentThread();
       owner.compareAndSet(current, null);
  }
}      

4 獨享 Vs 共享