ReentrantLock的实现原理

70 阅读2分钟

1 概念

重入锁: 表示支持重新进入的锁,调用 lock 方 法获取了锁之后,再次调用 lock,是不会再阻塞,内部直接增加重入次数 就行了,标识这个线程已经重复获取一把锁而不需要等待锁的释放,比如递归调用

2 lock锁用法

public class LockTest {
​
    static Lock  lock  = new ReentrantLock();
​
    public static void method1(){
        try{
            lock.lock();
            //进入method2方法
            method2();
            //可能会出现线程安全的操作
            System.out.println("method1...");
        }finally{
            //尽量在finally中释放锁
            lock.unlock();
        }
    }
​
    public static void method2(){
        try{
            lock.lock();
            //可能会出现线程安全的操作
            System.out.println("method2...");
        }finally{
            //尽量在finally中释放锁
            lock.unlock();
        }
    }
​
    public static void main(String[] args) {
        method1();
    }
}
  • lock()用来获取锁
  • unlock()释放锁,最好在finally块中释放
  • Lock 是 java.util.concurrent.locks.lock 包下的,是 api层面的锁

3 实现原理

ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似

构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。

查看ReentrantLock源码中的构造方法:

public ReentrantLock() {
    //非公平锁
    sync = new NonfairSync();
}
​
public ReentrantLock(boolean fair) {
    //公平锁
    sync = fair ? new FairSync() : new NonfairSync();
}

加锁解锁流程(非公平)

先从构造器开始看,默认为非公平锁实现

public ReentrantLock() {
    //非公平锁
    sync = new NonfairSync();
}

image.png

NonfairSync继承自 AQS,也就说底层主要实现也是基于AQS

ReentrantLock加锁和解锁过程.jpg

非公平锁体现

当某一个线程释放锁之后,会从AQS维护的双向队列中激活一个线程去抢锁(下图中的Thread-1),这个时候同时有一个新的线程(下图中的Thread-4)抢锁,如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread(锁拥有者),state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

image.png

公平锁体现

上图的情况就变为,Thread-4会乖乖去队列中等待被激活。Thread-1会抢到锁