1. 程式人生 > 其它 >2020-12-30

2020-12-30

技術標籤:Java併發程式設計併發

可重入鎖 ReentrantLock 及其他顯式鎖相關問題

1、跟 Synchronized 相比,可重入鎖 ReentrantLock 其實現原理有什麼不同

(1)底層實現
synchronized是JVM層面的鎖,是java關鍵字。而ReentrantLock是API層面的互斥鎖。

(2)是否可以手動釋放
synchronized不需要手動釋放,程式碼執行完之後系統自動讓執行緒釋放對鎖的佔用;

ReentrantLock則需要使用者去手動釋放鎖,如果沒有手動釋放鎖,可能會導致死鎖。一般通過lock()和unlock()方法配合try/finally語句來實現。

 public class testReentrantLock{
    private Lock lock = new ReentrantLock();
	public void increment() throws Exception {
	        lock.lock();
	        try {
	           ...........
	        } catch (Exception e) {
	            e.printStackTrace();
	        } finally {
	            lock.unlock();
	        }
	    }
}

(3)是否可中斷
synchronized是不可中斷的型別,除非加鎖的程式碼中出現異常或正常執行完成。
ReentrantLock是可以中斷的,可以通過 tryLock(long timeout, TimeUnit unit)方法設定超時,或者呼叫lockInterruptibly()方法進行中斷。
原始碼:
tryLock(long timeout, TimeUnit unit)

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

lockInterruptibly()

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

(4)是否公平鎖
Synchronized是非公平鎖。
ReentrantLock預設是非公平鎖,但是也可以通過引數設定為公平鎖。通過構造方法傳入boolean型別引數,false預設是非公平鎖,true為公平鎖。

原始碼:

// 預設是非公平鎖
public ReentrantLock() {
        sync = new NonfairSync();
}

// 根據傳入Boolean型別決定公平鎖還是非公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

(5)鎖是否可以繫結Condition
synchronized不能繫結
ReentrantLock可以繫結,需要結合await() 、signal()、signalAll()等方法進行操作。
await方法類似Object類的wait()方法,阻塞一個執行緒;signal()、signalAll()類似Object類的notify()/notifyAll()喚醒一個執行緒。

每一個Lock可以有任意資料的Condition物件,Condition是與Lock繫結的。Condition的強大之處在於它可以為多個執行緒間建立不同的Condition。

比如,使用Condition實現生產者-消費者模型:
其中一個是生產者,用於將訊息放入緩衝區;消費者從緩衝區取資料。

現在有一個問題,當緩衝區滿了的時候,而此時生產者還要往緩衝區放資料的時候,此時解決辦法就是讓生產者休眠,等消費者從緩衝區取出一個數據之後,再喚醒生產者。
同樣的,當緩衝區已經空了,而消費者還要來取資料時,此時要讓消費者進行休眠,等待生產者放入一個數據之後再喚醒消費者。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();//鎖物件
   final Condition notFull  = lock.newCondition();//寫執行緒條件 
   final Condition notEmpty = lock.newCondition();//讀執行緒條件 

   final Object[] items = new Object[100];//快取佇列
   int putptr/*寫索引*/, takeptr/*讀索引*/, count/*佇列中存在的資料個數*/;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)//如果佇列滿了 
         notFull.await();//阻塞寫執行緒
       items[putptr] = x;//賦值 
       if (++putptr == items.length) putptr = 0;//如果寫索引寫到佇列的最後一個位置了,那麼置為0
       ++count;//個數++
       notEmpty.signal();//喚醒讀執行緒
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)//如果佇列為空
         notEmpty.await();//阻塞讀執行緒
       Object x = items[takeptr];//取值 
       if (++takeptr == items.length) takeptr = 0;//如果讀索引讀到佇列的最後一個位置了,那麼置為0
       --count;//個數--
       notFull.signal();//喚醒寫執行緒
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

(6)鎖的物件
synchronized鎖的是物件,鎖是儲存在物件頭裡面的,根據物件頭標識是否有執行緒獲得鎖/爭搶鎖。
ReentrantLock鎖的是執行緒,根據進入的執行緒和int型別的state標識鎖的獲得/爭搶。

ReentrantLock 是如何實現可重入性的?