java多線程:ReentrantLock

在java中,除了用synchronized控制線程之外,還可以用ReentLock來控制。

synchronized簡(jiǎn)單方便,但是適用場(chǎng)景比較單一,局限比較多。ReentrantLock則靈活得多,具有時(shí)間鎖等待、可中斷鎖等功能。

synchronized是JVM實(shí)現(xiàn)的,而Lock是代碼層面實(shí)現(xiàn)的。
synchronized封裝了線程信息,便于調(diào)試,而Lock則并不知道自己當(dāng)前所在線程是哪一個(gè)。

需要注意的是,synchronized自己實(shí)現(xiàn)了鎖的釋放,所以我們執(zhí)行完操作時(shí),并不需要特意去釋放鎖,而Lock并沒有,所以我們需要在拿到鎖之后,需要在finally中執(zhí)行鎖的釋放,如果忘記了,很容易出現(xiàn)問題。

另外,從性能方面來說,高并發(fā)的情況,Lock的性能好過synchronized。

簡(jiǎn)單介紹下ReentrantLock的使用

lock

嘗試獲取鎖,如果拿不到,則一直等待,效果等同于synchronized

Lock lock = new ReentrantLock();
//創(chuàng)建一個(gè)重入鎖
lock.lock();
//申請(qǐng)鎖,此步驟往下都是鎖住范圍
try{
  //拿到鎖了,執(zhí)行操作
}finally{
  lock.unlock();
  //釋放鎖,在執(zhí)行完操作之后,手動(dòng)釋放鎖
}

tryLock

嘗試獲取鎖,如果拿不到,則不等待鎖的釋放,直接執(zhí)行另一操作

Lock lock = new ReentrantLock();
if (lock.tryLock()) {   
    try {   
        // 拿到鎖了,執(zhí)行操作   
    } finally {   
        lock.unlock();   
    }   
} else {   
// 沒拿到鎖,執(zhí)行操作 
} 

tryLock還有一個(gè)重載方法,可以傳入等待時(shí)間

Lock lock = new ReentrantLock();
if (lock(5, TimeUnit.SECONDS)) {  
  //試圖在五秒內(nèi)獲取鎖,若獲取不到,返回false
    try {   
        // 拿到鎖了,執(zhí)行操作   
    } finally {   
        lock.unlock();   
    }   
} else {   
// 沒拿到鎖,執(zhí)行操作 
} 

lockInterruptibly

在synchronized中,如果申請(qǐng)的對(duì)象鎖被其他線程獲取了,那么此線程會(huì)一直等待直到對(duì)象鎖被釋放,并且無法終止等待。那么在Lock中,lockInterruptibly方法表示,申請(qǐng)鎖,并且可以中斷申請(qǐng)。

public class SleepThread extends Thread{
    
    private ReentrantLock lock;
    
    public SleepThread(ReentrantLock lock){
        this.lock=lock;
    }
    
    @Override
    public void run() {
        super.run();
        lock.lock();
        try {
            sleep(100000);
          //拿到鎖之后睡覺
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}

public class InterruptiblyThread extends Thread{
    
    private ReentrantLock lock;
    
    public InterruptiblyThread(ReentrantLock lock){
        this.lock=lock;
    }
    
    @Override
    public void run() {
        super.run();
        try {
            lock.lockInterruptibly();
             //申請(qǐng)鎖,此過程可以被中斷
            try{
                
            }finally{
                lock.unlock();
            }
        } catch (InterruptedException e) {
            //阻塞被喚醒并被要求處理中斷
            //抓住或者拋出中斷
            System.out.println("我被中斷啦");
        }
        
    }
}

public static void main(String[] args) {
        ReentrantLock lock=new ReentrantLock();
        Thread sleep=new SleepThread(lock);
        Thread inter=new InterruptiblyThread(lock);
        
        sleep.start();
        inter.start();
        inter.interrupt();
    }

我被中斷啦

等待與喚醒

在synchronized中,線程通信用Object的wait、notify、notifyAll方法來實(shí)現(xiàn)。并且對(duì)象鎖的喚醒與等待,是一一對(duì)應(yīng)的。這種情況下,如果我只想喚醒其中某部分線程,那就必須用到多個(gè)對(duì)象鎖了。

Lock的本身實(shí)現(xiàn)了線程的通信方法,通過Condition的await、signal、signalAll來分別對(duì)應(yīng)Object的方法。并且一個(gè)Lock可以和多個(gè)Condition關(guān)聯(lián)。

舉個(gè)栗子:

ReentrantLock lock = new ReentrantLock(true);
Condition empty = lock.newCondition();
Condition full = lock.newCondition();

public void lockConsume() {
  // 如果隊(duì)列空了,則消費(fèi)者等待
  if (TaskQueue.getInstance().isEmpty()) {
    lock.lock();
    try {
      if (TaskQueue.getInstance().isEmpty()) {
        // 生產(chǎn)者喚醒,這里只能喚醒由full.await進(jìn)入等待的線程
        full.signal();
        try {
          //消費(fèi)者等待
          empty.await();
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    } finally {
      lock.unlock();
    }
  } else {
    //消費(fèi)
    TaskQueue.getInstance().getTask();
  }
}

private void lockProduce() {
  // 如果隊(duì)列滿了,生產(chǎn)者暫停
  if (TaskQueue.getInstance().isFull()) {
    lock.lock();
    try {
      if (TaskQueue.getInstance().isFull()) {
        // 消費(fèi)者喚醒,這里同樣只能喚醒由empty.await進(jìn)入等待的線程
        empty.signal();
        try {
          //生產(chǎn)者暫停
          full.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    } finally {
      lock.unlock();
    }
  } else {
   //生產(chǎn)
    Bean b = new Bean();
    TaskQueue.getInstance().addTask(b);
  }
}

我用兩個(gè)Condition控制了多個(gè)線程,而Lock卻只有一個(gè)。Condition的方法和Object并沒有什么功能上的不同,只不過更加靈活。

公平性(FIFO)

大家可能注意到上面等待與喚醒中舉的栗子,初始化Lock的時(shí)候和之前的栗子不同,傳入了一個(gè)布爾參數(shù),這個(gè)參數(shù)就是用來控制Lock的公平性,即是否先入先出(默認(rèn)為false)。

如果Lock是公平的,那么線程執(zhí)行的順序會(huì)嚴(yán)格按照請(qǐng)求鎖的順序依次獲得鎖,而如果不是,則線程可能比其他先請(qǐng)求鎖的線程先得到鎖。

Condition和Lock是綁定的,所以Condition也有Lock的公平性,Condition按照await的順序一一喚醒。這里Condition按照自己的await隊(duì)列喚醒之后,添加到Lock申請(qǐng)鎖的隊(duì)列中,依然按照FIFO獲得鎖。

從性能上來說,公平性比非公平性的性能低,因?yàn)镴VM無法根據(jù)條件調(diào)度線程,只能嚴(yán)格執(zhí)行隊(duì)列順序。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容