月泉的博客

JUC-ReentrantLock原理剖析

月泉 并发编程JUC

阅读该篇文章希望您已经理解或者了解过AQS的设计,因为本篇并不会重复叙述AQS的内容,若需要可阅读我写过的AQS文章或者其它文章。

ReentrantLock是什么

ReentrantLock是一个显式独占可重入的公平或非公平锁,使用时可以设置锁是公平锁或非公平锁,它们之间的区别在于,线程唤醒的抢占方式,公平锁线程的抢占方式按照获取锁的先后顺序来唤醒,而非公平锁就是以一种随机抢占的方式。

ReentrantLock怎么用

public class ReentrantLockTest {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        new Thread(new Task("task1",lock)).start();
        new Thread(new Task("task2",lock)).start();
        new Thread(new Task("task3",lock)).start();
        new Thread(new Task("task4",lock)).start();
        new Thread(new Task("task5",lock)).start();
        new Thread(new Task("task6",lock)).start();
        new Thread(new Task("task7",lock)).start();

    }

    static final class Task implements Runnable{
        private String name;
        private Lock lock;

        public Task(String name, Lock lock) {
            this.name = name;
            this.lock = lock;
        }

        @Override
        public void run() {
            lock.lock();
            try{
                System.out.println("The " + name + " start execute");
                System.out.println("The " + name + " end executed");
            }finally {
                lock.unlock();
            }
        }
    }
}

在默认情况下创建ReentrantLock实例创建的是一把非公平锁,如果要创建公平锁可使用有参的构造函数

new ReentrantLock(true)

ReentrantLock的实现原理

基本类结构

public class ReentrantLock implements Lock, java.io.Serializable {
    abstract static class Sync extends AbstractQueuedSynchronizer {
        ......
    }
    
    static final class NonfairSync extends Sync {
        ......
    }
    
    static final class FairSync extends Sync {
        ......
    }
    
    public ReentrantLock() {
        ......
    }
    
    public ReentrantLock(boolean fair) {
        ......
    }
}

其实现了接口Lock并创建了3个内部类,一个抽象的Sync内部类和2个继承至这个抽象类的NonfairSyncFairSync分别代表非公平同步器和公平同步器

先从非公平的写起,首先看这个类的无参构造函数

public ReentrantLock() {
    sync = new NonfairSync();
}

默认的无参构造函数会创建一个NofairSync的实例

然后我们在使用锁的时候通常会使用lockunlock方法,我们看其类的实现

public void lock() {
    sync.acquire(1);
}
public void unlock() {
    sync.release(1);
}

可以从源码上看到实际上就是调用的同步器中的acquirerelease的实例方法.

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

NofairSync中覆盖了父类的tryAcquire方法,调用了父类的nonfairTryAcquire方法,看过我AQS那篇文章应该知道调用acquire方法时里面会调用tryAcquire方法

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread(); // 1
    int c = getState(); // 2
    if (c == 0) { // 3
        if (compareAndSetState(0, acquires)) { // 4
            setExclusiveOwnerThread(current); // 5
            return true; // 6
        }
    }
    else if (current == getExclusiveOwnerThread()) { // 7
        int nextc = c + acquires; // 8
        if (nextc < 0) // 9
            throw new Error("Maximum lock count exceeded");
        setState(nextc); // 10
        return true;
    }
    return false;
}
  1. 获取当前线程
  2. 获取同步器中的state
  3. 判断状态是否为0(如果为0则锁未被其它线程占有)
  4. 更改线程占有标识
  5. 设置线程占有为当前线程
  6. 返回true获取成功
  7. 否则判断当前占有线程是否是现在要获取锁的线程(如果是代表线程重入锁)
  8. 获取得到当前重入后的重入数
  9. 小于0则达到最大重入数,抛出异常
  10. 否则设置状态

接着看释放锁的子类实现逻辑

protected final boolean tryRelease(int releases) {
    int c = getState() - releases; // 1
    if (Thread.currentThread() != getExclusiveOwnerThread()) // 2
        throw new IllegalMonitorStateException();
    boolean free = false; // 3
    if (c == 0) { // 4
        free = true; /// 5
        setExclusiveOwnerThread(null); // 6
    }
    setState(c); // 7
    return free;
}
  1. 当前状态减release
  2. 判断当前线程是否是当前独占持有线程,如果不是抛出异常IllegalMonitorStateException
  3. 是否锁成功标识
  4. 判断减后状态是否为0
  5. 释放成功标识为true
  6. 将当前独占持有线程置空
  7. 设置state(重入数量)
  8. 返回标识

可见非公平锁的实现机制非常简单,就是将AQS的state做为重入数

接着看下公平锁

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    @ReservedStackAccess
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread(); // 1
        int c = getState(); // 2
        if (c == 0) { // 3
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) { // 4
                setExclusiveOwnerThread(current); // 5
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 6
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

可以看到公平锁重写了父类的tryAcquire方法

  1. 获取当前线程
  2. 获取AQS的state
  3. 判断当前锁是否被持有
  4. 调用父类AQS的hasQueuedPredecessors判断当前的线程是否前面还有排队的线程,如果没有就更改状态值
  5. 设置当前独占线程为当前线程
  6. 判断当前获取锁的线程是否是当前独占锁的线程,如果是就走重入锁步骤
月泉
伪文艺中二青年,热爱技术,热爱生活。