Java并发Lock之ReentrantLock实现原理

447 阅读8分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

往期推荐

一、ReentrantLock

ReentrantLock也称作可重入锁,是在Java并发编程中常见的一种并发编程工具,通常用于保障代码段线程安全,ReentrantLock的实现主要依靠AbstractQueuedSynchronizer类。

class ReentrantLockDemo{
    /**
     * 锁对象
     */
    private static Lock lock;
    public  static  void main(String[] args)throws  InterruptedException{
        System.out.println("ReentrantLock 公平锁使用方式演示:");
        //公平锁模式
        lock =new ReentrantLock(true);
        for (int i=0; i<5;i++){
            new  Thread(new ThreadDemo(i)).start();
        }
        Thread.sleep(1000);
        System.out.println("ReentrantLock 非公平锁使用方式演示:");
        //非公平锁模式
        lock =new  ReentrantLock(false);
        for (int i=0; i<5;i++){
            new  Thread(new ThreadDemo(i)).start();
        }
    }
    /**
     * 实现Runnable接口
     */
    static  class  ThreadDemo implements  Runnable{
        Integer id;
        public  ThreadDemo(Integer id){
            this.id=id;
        }

        @Override
        public  void run(){
            try{
                TimeUnit.MILLISECONDS.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            for (int i=0;i<2;i++){
                lock.lock();
                System.out.println("获得锁的线程:"+id);
                lock.unlock();
            }
        }
    }
}
ReentrantLock 公平锁使用方式演示:
获得锁的线程:4
获得锁的线程:0
获得锁的线程:3
获得锁的线程:2
获得锁的线程:1
获得锁的线程:4
获得锁的线程:0
获得锁的线程:3
获得锁的线程:2
获得锁的线程:1
ReentrantLock 非公平锁使用方式演示:
获得锁的线程:3
获得锁的线程:3
获得锁的线程:1
获得锁的线程:1
获得锁的线程:2
获得锁的线程:2
获得锁的线程:4
获得锁的线程:4
获得锁的线程:0
获得锁的线程:0

二、ReentrantLock的三大内部类

2.1 ReentrantLock类图

ReentrantLock类内部共存在SyncNonfairSyncFairSync三个内部类。

  • NonfairSyncFairSync类继承自Sync类
  • Sync类继承自AbstractQueuedSynchronizer抽象类

155.png

2.2 Sync类

/**
 * 可重入锁基础的同步控制器
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * 加锁方法,由Sync的子类FairSync、NonfairSync实现
     */
    abstract void lock();

    /**
     * 非公平锁
     */
    final boolean nonfairTryAcquire(int acquires) {
        //当前线程
        final Thread current = Thread.currentThread();
        //获取锁的状态,即AQS中的state状态
        int c = getState();
        // c等于0 表示没有线程持有该锁
        if (c == 0) {
            //CAS修改state状态,如果CAS成功,即加锁成功
            if (compareAndSetState(0, acquires)) {
                //设置当前线程以独占的方式持有锁
                setExclusiveOwnerThread(current);
                //返回加锁成功
                return true;
            }
        }
        //如果c不等于0,表示此时锁被某个线程占有
        //如果当前的线程就是锁的持有者
        else if (current == getExclusiveOwnerThread()) {
            //发生重进入,增加重进入的次数
            int nextc = c + acquires;
            //如果 nextc 小于0,即发生了int类型的溢出
            if (nextc < 0)
                // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
   
    // 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程不为独占线程
            throw new IllegalMonitorStateException(); // 抛出异常
        // 释放标识
        boolean free = false; 
        if (c == 0) {
            free = true;
            // 已经释放,清空独占
            setExclusiveOwnerThread(null); 
        }
        // 设置标识
        setState(c); 
        return free; 
    }
    
    // 判断资源是否被当前线程占有
    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    // 新生一个条件
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class
    // 返回资源的占用线程
    final Thread getOwner() {        
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    // 返回状态
    final int getHoldCount() {            
        return isHeldExclusively() ? getState() : 0;
    }

    // 资源是否被占用
    final boolean isLocked() {        
        return getState() != 0;
    }

    /**
        * Reconstitutes the instance from a stream (that is, deserializes it).
        */
    // 自定义反序列化逻辑
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}  

Sync类方法和作用:

方法作用
lock加锁方法,由Sync的子类FairSync、NonfairSync实现
nonfairTryAcquire非公平加锁
tryRelease试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态
isHeldExclusively检验当前的线程是否与持有锁的线程相等
newCondition返回一个ConditionObject对象
getOwner返回锁的持有者
getHoldCount返回重入锁重进入的次数
isLocked判断是否已经被加锁
readObject通过流反序列化对象

2.3 FairSync类

FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法

/**
 * FairSync继承Sync,实现公平锁
 */
static final class FairSync extends ReentrantLock.Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /**
     * 加锁
     */
    final void lock() {
        acquire(1);
    }

    /**
     * 调用子类的tryAcquire()实现
     */
    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取状态
        int c = getState();
        if (c == 0) { // 状态为0
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
                // 设置当前线程独占
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
            // 下一个状态
            int nextc = c + acquires;
            if (nextc < 0) // 超过了int的表示范围
                throw new Error("Maximum lock count exceeded");
            // 设置状态
            setState(nextc);
            return true;
        }
        return false;
    }
}

2.4 NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法

// 非公平锁
static final class NonfairSync extends Sync {
    // 版本号
    private static final long serialVersionUID = 7316153563782823691L;

    // 获得锁
    final void lock() {
        if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
            // 把当前线程设置独占了锁
            setExclusiveOwnerThread(Thread.currentThread());
        else // 锁已经被占用,或者set失败
            // 以独占模式获取对象,忽略中断
            acquire(1); 
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

NonfairFairSync的区别

  • FairSync类的lock的方法中可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。
  • Nonfair类的lock的方法每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。

三、公平锁和非公平锁

3.1 公平锁

ReentrantLock以公平锁方式进行加锁时,调用的FairSync类的lock()方法调用AbstractQueuedSynchronizer类的acquire()方法实现对资源的加锁。

/**
* FairSync 方式加锁
*/
final void lock() {
    acquire(1);
}
/**
* AQS的acquire()方法
*/
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

接着AbstractQueuedSynchronizer类的tryAcquire()方法是需要其子类实现的,若FairSync的tryAcquire()方法返回true,当前线程加锁成功,则ReentrantLock的公平锁模式加锁成功;否则ReentrantLock公平锁模式加锁失败。

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

ReentrantLock公平锁执行流程图:

142.png ReentrantLock锁的释放是通过unlock()方法实现的,调用AbstractQueuedSynchronizer类的release()方法实现解锁,ReentrantLock的内部类Sync实现了tryRelease()方法

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

3.2 非公平锁

ReentrantLock的非公平加锁是通过调用NonfairSync类的lock()方法实现的。lock()方法将会进入AbstractQueuedSynchronizer中的acquire()方法执行。

 final void lock() {
      if (compareAndSetState(0, 1))
           setExclusiveOwnerThread(Thread.currentThread());
        else
           acquire(1);
        }
        
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

NonfairSync中的tryAcquire()方法调用其父类SyncnonfairTryAcquire()方法。 如果nonfairTryAcquire()方法返回false,即非公平锁加锁失败,程序就会执行到AbstractQueuedSynchronizer类的addWaiter()方法,接下来将调用以下代码(后面的流程与公平锁加锁失败流程类似:

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

ReentrantLock非公平锁执行流程图:

156.png

四、synchronized 和 ReentrantLock 区别

synchronized 和 ReentrantLock 的异同,它们之间的相同点如下:

  • 都可以用于实现线程间的同步访问;
  • 两者都是可重入锁,即一个线程能够对资源重复加锁;

其不同点如下:

  • 同步实现机制不同:

    • synchronized 通过 Java 对象关联的 Monitor 监视器实现(不考虑偏向锁、轻量级锁);
    • ReentrantLock 通过 CASAQSLockSupport 等共同实现;
  • 可见性实现机制不同:

    • synchronized 依赖 JVM 内存模型保证包含共享变量的多线程内存可见性。
    • ReentrantLock 通过 ASQvolatile 类型的 state 同步状态值保证包含共享变量的多线程内存可见性。
  • 使用方式不同:

    • synchronized 可以用于修饰实例方法(锁住实例对象)、静态方法(锁住类对象)、同步代码块(指定的锁对象)。
    • ReentrantLock 需要显式地调用 lock 方法,并在 finally 块中释放。
  • 功能丰富程度不同:

    • synchronized 只提供最简单的加锁。
    • ReentrantLock 提供定时获取锁、可中断获取锁、Condition(提供 awaitsignal 等方法)等特性。
  • 锁类型不同:

    • synchronized 只支持非公平锁。
    • ReentrantLock 提供公平锁和非公平锁实现。但非公平锁相比于公平锁效率较高。

一般来说,仅当需要使用 ReentrantLock 提供的其他特性时,例如:可中断的、可定时的、可轮询的、公平地获取锁等,才考虑使用 ReentrantLock。否则应该使用 synchronized,简单方便。

参考资料

深入理解 ReentrantLock ReentrantLock 源码分析 (基于Java 8)