JUC - ReentrantLock 的基本用法 以及 lock()、tryLock()、lockInterruptibly()的區別


ReentrantLock 與 synchronized對比

最近有在閱讀Java並發編程實戰這本書,又看到了ReentrantLock和synchronized的對比,發現自己以前對於RenntrantLock的理解很片面,特此做一番總結,如果有總結不到位的,歡迎指出

java.util.concurrent.locks 
接口 Lock

所有已知實現類:
ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock

為什么需要Lock?

java.util.concurrent.locks 
接口 Lock

所有已知實現類:
ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock

Lock提供了一種如條件的、可輪詢的、定時的以及可以終端的獲取鎖的操作,所有的加鎖方式和解鎖方式都是顯式的。

public class Lock {
    private boolean locked = false;

    public Lock() {
    }

    public final synchronized void lock() throws InterruptedException {
        while(this.locked) {
            this.wait();
        }

        this.locked = true;
    }

    public final synchronized void unlock() {
        this.locked = false;
        this.notifyAll();
    }
}

 

 Lock是JAVA5.0出現的,它的出現並不是為了替代synchronized,而是在synchronized不適用的時候使用。

那么synchronized有什么局限性呢?

  • 無法中斷一個正在獲取鎖的線程

當一個線程想獲取已經被其他線程持有的鎖時,就會發生堵塞,假設已經持有鎖的線程一直不釋放鎖,那么線程就會一直等待下去。

  •  無法指定獲得鎖的等待時間

比如,想要A線程執行某個操作,想在指定時間內A線程沒有獲取到鎖就返回。synchronized是做不到的。

相同點:

  • 獨占鎖: 一次只允許一個線程訪問
  • 可重入鎖: 一個線程可重復獲得自己已獲得鎖,不會發生死鎖。簡單來說,遞歸的時候不會發生死鎖

不同點:

  • Lock不是java內置的,synchronized是JVM內置的,因此是內置特性。
  • 釋放鎖的方式:
    • Lock 必須要在finally中手動釋放鎖
    • synchronized 會根據鎖區域代碼自動執行完畢,或者發生異常,JVM會自動釋放鎖
  • 公平:
    • Lock是可公平可不公平鎖
    • synchronized是不公平鎖        

ReentrantLock的使用:

基本使用 

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

 

  • lock: 調用后一直阻塞直到獲得鎖。
package com.amber;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestReentrantLock {
    Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        TestReentrantLock testReentrantLock = new TestReentrantLock();
        new Thread(() -> {
            try {
                testReentrantLock.testConcurrency(Thread.currentThread());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "線程1").start();

        new Thread(() -> {
            try {
                testReentrantLock.testConcurrency(Thread.currentThread());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "線程2").start();
    }

    private void testConcurrency(Thread thread) throws InterruptedException {
        //獲取鎖成功返回true,如果獲取失敗,等待2S,規定時間內還是沒有獲得鎖,那么就返回false
        if (lock.tryLock( 2000, TimeUnit.MICROSECONDS)) {
            try {
                System.out.println(thread.getName() + " :  " + "獲取鎖");
                Thread.sleep(3000);
            } finally {
                System.out.println(thread.getName() + "釋放鎖");
                lock.unlock(); //一定記得要釋放鎖
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + "等待了,沒有獲取鎖");
        }
    }
}

 

  • tryLock:拿到鎖返回true,否則false;帶有時間限制的tryLock(long time, TimeUnit timeUnit),拿不到鎖,就等待一段時間,超時返回false
  • lockInterruptibly :調用后如果沒有獲取到鎖會一直阻塞,阻塞過程中會接受中斷信號。
 lockInterruptibly有點難以理解,假設A線程想去獲取鎖,但是鎖被B線程持有,那么A就會發生堵塞。
A堵塞的時候,可以有以下兩種方法發生狀態改變:
  1. A獲取鎖資源
  2. A被其他線程中斷:
    1. 這里只得被其他線程中斷的意思是,C線程調用A線程的interrupt()。那么此時A線程就會被喚醒,處理中斷信號。

  lockInterruptibly是被中斷,就由阻塞狀態被喚醒去處理中斷信號。

 在JAVA並發編程實戰這本書中還提到了ReentrantLock的一個重要用法,那就是輪詢鎖。下面是書中的源代碼:

 1 public boolean transferMoney(Account fromAcct, Account toAcct, DollarAmount amount,  long timeout, TimeUnit unit) throws InsufficientFundsException, InterruptedException {
 2     long fixedDelay = 1;
 3     long randMod = 2;
 4     long stopTime = System.nanoTime() + unit.toNanos(timeout);
 5     while (true) {
 6         if (fromAcct.lock.tryLock()) {
 7             try {
 8                 if (toAcct.lock.tryLock()) { //如果不能同事獲得兩個鎖,那么線程就會釋放已經獲得的鎖。這樣可以很有效的解決死鎖問題。
 9                     try {
10                         if (fromAcct.getBalance().compareTo(amount) < 0)
11                             throw new InsufficientFundsException();
12                         else{
13                             fromAcct.debit(amount);
14                             toAcct.credit(amount);
15                             returntrue;
16                         }
17                     } finally {
18                         toAcct.lock.unlock();
19                     }
20                 }
21             } finally {
22                 fromAcct.lock.unlock();
23             }
24         }
25         if (System.nanoTime() < stopTime)
26             returnfalse;
27         NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
28     }
29 }

 

程序發生死鎖的時候,往往只能通過重新啟動程序解決。而有時候因為獲取鎖的時序不一致,很容易發生死鎖。根據上述代碼第6行和第8行,假設此時我們使用的synochronized內置鎖,A線程從cc賬號轉賬到dd賬號,B線程從dd賬號轉賬到cc賬號,就很容易發生死鎖。但是使用tryLock()卻可以避免鎖順序造成死鎖的問題,

如果線程A、B不能同時獲取cc和dd對象的鎖,那么就會放棄自己已經獲得的鎖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM