Concurrency-In-depth analysis of the implementation principle of ReentrantLock

I. Introduction

  Before spending some time to study an important component under the concurrent package-the abstract queue synchronizer AQS, in the concurrent package, many classes are based on it, including Javathe commonly used locks ReentrantLock. Knowing the AQSrealization principle, the realization of the understanding ReentrantLockis very simple, because its lock function is realized by the AQSrealization, and its job is only to rewrite some AQSof the related methods, and use the template method to add The lock is unlocked. Today's blog will analyze ReentrantLockthe implementation from the perspective of source code .


Second, the text

2.1 Abstract queue synchronizer AQS

  Before we talk ReentrantLock, we must mention it first AQS. AQSFull name Abstract Queue Synchronizer (AbstractQuenedSynchronizer), it is a basic framework that can be used to achieve thread synchronization. Of course, it is not Springsuch a framework as we understand , it is a class, the class name is AbstractQuenedSynchronizer, if we want to implement a lock or similar synchronization component that can complete thread synchronization, it can be used AQSto achieve it, because it encapsulates threads Synchronous way, we use it in our own class, we can easily achieve a lock of our own.

  AQSThe implementation of is relatively complicated and cannot be made clear in just a few words. I have previously written a AQSblog dedicated to the analysis of implementation principles: concurrency-the implementation principle of the abstract queue synchronizer AQS .

  Before reading the following content, please be sure to learn the implementation principle of AQS , because ReentrantLockthe implementation is very simple, it is completely dependent on it AQS, so my following descriptions are all AQSbased on the basis of understanding . You can read the recommended blog above, or you can check the relevant information yourself.


2.2 The realization principle of ReentrantLock

  Let's first briefly introduce ReentrantLockthe implementation principle, so that we can read its source code below. As mentioned earlier, ReentrantLockbased on the AQSimplementation of AQSthe template methodacquire , releaseetc., the locking and unlocking operations have been implemented, and the class that uses it only needs to override the methods called in these template methods, such as tryAcquire, tryReleaseetc., these methods are modified AQSby Synchronized state stateto lock and unlock. AQSThe synchronization status stateis a inttype of value. According to different values, you can determine the current lock status. Modifying this value is the way to lock and unlock.

  The use of AQSgeneral internal class approach in the form of inheritance AQS, ReentrantLockis so realized, in its interior, with three AQSof derived classes:

  1. First, the name of the first derived class is called Sync , which is an abstract class that directly inherits from it AQS, which defines some common methods;
  2. The second derived class is called NonfairSync , which inherits from it Syncand implements an unfair lock ;
  3. The third derived class is named FairSync , which is also inherited from it Syncand implements a fair lock ;

  ReentrantLockIt is through NonfairSyncobjects or FairSyncobjects to ensure thread synchronization. The methods written in these three classes are actually ways to modify the synchronization state. When statethe value of 0time, and do not represent the current thread to acquire the lock, and each acquire a lock, statethe value will +1release a lock stateon -1. Let's take a closer look at the source code of these three categories.


2.3 Sync source code analysis

  Let's take a look directly at Syncthe methods in the class. There are many methods in the Sync class. I will only come up with some of the more important ones:

abstract static class Sync extends AbstractQueuedSynchronizer {
	/** 定义一个加锁的抽象方法,由子类实现 */
    abstract void lock();

    /**
     * 此方法的作用是以非公平的方式尝试获取一次锁,获取成功则返回true,否则返回false;
     * 需要注意,AQS的获取锁,实际上就是修改同步状态state的值。
     * 这里有个疑惑,既然是非公平地获取锁,那这个方法为什么不写在NonfairSync类中?
     * 因为ReentrantLock有一个方法tryLock,即尝试获取一次锁,调用tryLock方法时,
     * 无论使用的是公平锁还是非公平锁,实际上都需要尝试获取一次锁,也就是调用这个方法,
     * 所以这个方法定义在了父类Sync中
    */
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前正在运行的线程
        final Thread current = Thread.currentThread();
        // 获取同步状态state的值,state定义在父类AQS中,
        int c = getState();
        // 若当前state的值为0,表示还没有线程获取锁,于是当前线程可以尝试获取锁
        if (c == 0) {
            // compareAndSetState方法通过CAS的方式修改state的值,
            // 实际上就是让state从0变为1,因为acquires的值就是1,
            // 每次有线程获取了锁时,同步状态就+1
            if (compareAndSetState(0, acquires)) {
                // 若compareAndSetState方法返回true,表示修改state成功,
                // 则调用setExclusiveOwnerThread方法将当前线程记录为占用锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 若以上c == 0不满足,则表示已经有线程获取锁了,
        // 于是调用getExclusiveOwnerThread方法获取当前正在占用锁的线程,
        // 然后和当前线程比较,若当前线程就是占用锁的线程,则当前线程不会被阻塞,
        // 可以再次获取锁,从这里可以看出,ReentrantLock是一个可重入锁
        else if (current == getExclusiveOwnerThread()) {
            // 计算当前线程获取锁后,state的值应该是多少,实际上就是让state + 1
            int nextc = c + acquires;
            // 如果nextc小于0,则保存,因为理论上同步状态是不可能小于0的
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 使用上面计算出的nextc更新state的值,这里需要注意一点
            // setState不像compareAndSetState方法,setState方法并不保证操作的原子性
            // 这里不需要保证原子性,因为这里线程已经获取了锁,所以不会有其他线程进行操作
            setState(nextc);
            // 返回true表示加锁成功
            return true;
        }
        // 若以上条件均不满足,表示有其他线程获取了锁,当前线程获取锁失败
        return false;
    }

    
    /**
     * 此方法是的作用是尝试释放锁,其实也就是让state的值-1
     * 这个方法是一个通用的方法,不论使用的是公平锁还是非公平锁
     * 释放锁时都是调用此方法修改同步状态
     */
    protected final boolean tryRelease(int releases) {
        // getState方法获取state的值,并与参数相减,计算释放锁后state的值
        // 在ReentrantLock中其实就是-1
        int c = getState() - releases;
		// 判断当前线程是不是占用锁的线程,若不是则抛出异常
        // 因为只有占用了锁的线程才可以释放锁
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        
        // 变量free用来标记锁释放真正的被释放,因为ReentranLock是一个重入锁
        // 获取锁的线程可以多次获取锁,只有每一次获取都释放,锁才是真正的释放
        boolean free = false;
        // 判断c的值是否是0,只有c的值是0,也就是state的值为0时
        // 才说明当前的线程在这次释放锁后,锁真正的处于没有被使用的状态
        if (c == 0) {
            // 若满足此条件,则free标记为true,表示锁真的被释放了
            free = true;
            // 然后标记当前占用锁的线程为null,也就是没有线程占用锁
            setExclusiveOwnerThread(null);
        }
        // 将c的值更新同步状态state
        setState(c);
        return free;
    }
	
    /** 此方法判断当前线程是不是获取锁的线程 */
    protected final boolean isHeldExclusively() {
        // getExclusiveOwnerThread方法返回当前正在占用锁的线程
        // 于当前的运行的线程进行比较
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
}

  The above is Syncthe implementation of the class. In fact Sync, there are not only the above methods, but the remaining methods are all bits and pieces, which are ReentrantLocknot very helpful to our understanding , so I will not list them here. From the above method implementation, we can know the following information: the way that the thread acquires the lock is actually to increase the value of the synchronization state state, and the way to release the lock is to reduce the value of state; and ReentrantLock implements reentrancy Lock, the thread that has acquired the lock can acquire the lock again without hindrance, and the value of state can continue to increase. When the lock is released, the lock is really released only when the value of state decreases to 0 .


2.4 NonfairSync source code analysis

Here we look at the second inner class NonfairSyncthat implements a non-fair locks .

/**
 * 此类继承自Sync,它实现的是非公平锁
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    
    /**
     * 在父类Sync中定义的lock方法,在子类中实现
     */
    final void lock() {
        // 调用compareAndSetState方法,企图使用CAS机制将state的值从0修改为1
        // 若state的值不为0,表示锁已经被其他线程获取了,则当前线程将获取锁失败
        // 或者state的值一开始是0,但是在当前线程修改的过程中,被其他线程修改了,
        // 也会返回false。若修改state失败,则就需要执行下面的acquire方法
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        // acquire方法是AQS定义的模板方法,这个方法会调用tryAcquire尝试获取锁,
        // 而tryAcquire方法是由子类实现的,也就是下面那个方法;
        // 若调用tryAcquire获取锁失败,则AQS会将线程封装成一个节点Node
        // 丢入AQS的同步队列中排队(这个具体实现请参考AQS的实现博客)
        // 归根到底,这个方法就是让线程获取锁,不断尝试,直到成功为止.
        // 注意这里传入的参数是1,表示加锁实际上就是让state的值+1
        else
            acquire(1);
    }

    
    /** 
     * 此方法tryAcquire在AQS的模板方法中被调用,它的作用就是尝试获取一次锁,
     * 也就是尝试修改一次同步状态state;
     * 不同的实现类根据不同的需求重写tryAcquire方法,就可以按自己的意愿控制加锁的方式
     * AQS就是通过这种方式来提供给其他类使用的
     */
    protected final boolean tryAcquire(int acquires) {
        // 此处直接调用了父类Sync中,非公平地获取一次锁的nonfairTryAcquire方法
        return nonfairTryAcquire(acquires);
    }
}

  The above is the NonfairSynccomplete code of the class, and it has not been deleted. It can be seen that it is very short. Implemented the method Syncdefined in the class lock, and rewritten the tryAcquiremethod at the same time , for AQSthe template method to acquirecall, and tryAcquirethe implementation is only Syncthe nonfairTryAcquiremethod called . To help us understand, we take a look at AQSthe acquirecode for the method it:

public final void acquire(int arg) {
    // 这里首先调用tryAcquire方法尝试获取一次锁,在AQS中这个方法没有实现,
    // 而具体实现是在子类中,也就是调用的是NonfairSync的tryAcquire方法,
    // 若方法返回true,表示成功获取到锁,于是后面代码都不会执行了,
    // 否则,将先执行addWaiter方法,这个方法的作用是将当前线程封装成为一个Node节点,
    // 加入到AQS的同步队列的尾部,同时将返回这个Node节点,并传入acquireQueued方法
    // acquireQueued方法的作用就是让当前线程阻塞,直到成功获取到锁才会从这个方法返回
    // acquireQueued会返回这个线程在等待的过程中是否被中断,若被中断,
    // 则调用selfInterrupt方法真正执行中断。
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

  Why NonfairSyncis it unfair lock? We can see that in NonfairSyncthe lockmethod, before a thread tries to acquire the lock, it does not determine whether there is a thread waiting to acquire the lock before it, but directly attempts to call the compareAndSetStatemethod to acquire the lock. If the acquisition fails, enter the acquiremethod, In this method, the method will be called again to tryAcquireacquire the lock. At this time, if the acquisition fails again, it will be queued into the synchronization queue. During this process, two queues are inserted, so it NonfairSyncis an unfair lock.


2.5 FairSync source code analysis

  Let's take a look at the last inner class FairSync, which implements fair locks , that is, threads acquire locks in the order of first-come-first-served, without intervening:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /** 实现父类的lock方法,加锁 */
    final void lock() {
        // 直接调用AQS的模板方法acquire进行加锁,调用这个方法后,线程若没有获取锁
        // 则会被阻塞,直到获取了锁后才会返回。这里需要注意一点,和NonfairSync中的lock不同
        // 这里直接调用acquire,而不会先调用一次compareAndSetState方法获取锁
        // 因为FairSync是公平锁,所以不会执行这种插队的操作.
        // 注意这里传入的参数是1,表示加锁实际上就是让state的值+1
        acquire(1);
    }

    /** 
     * 和NonfairSync一样,重写AQS的tryAcquire方法,若使用的是FairSync,
     * 则acquire中将调用此tryAcquire方法,尝试获取一次锁
     */
    protected final boolean tryAcquire(int acquires) {
        // 首先获取当前正在执行的线程
        final Thread current = Thread.currentThread();
        // 记录同步状态
        int c = getState();
        // 若state的值为0,表示现在没有线程占用了锁,于是当前线程可以尝试获取锁
        if (c == 0) {
            // 尝试获取锁前,先调用hasQueuedPredecessors方法,这个方法是判断
            // 是否有其他线程正在排队尝试获取锁,若有,方法将返回true,那为了公平性,
            // 当前线程不能获取锁,于是直接结束,否则调用compareAndSetState修改state
            // 若修改成功,调用setExclusiveOwnerThread方法将自己设置为当前占用锁的线程
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 若state不等于0,则表示当前锁已经被线程占用,那此处判断占用锁的线程是否是自己
        // 若是,则当前线程可以再次获取锁,因为ReentrantLock实现的是可重入锁,
        else if (current == getExclusiveOwnerThread()) {
            // 计算当前线程再次获取锁后,state的值将变为多少,此处实际上就是 + 1
            int nextc = c + acquires;
            // 理论上state的值不可能小于0,于是若小于0,就报错
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            // 修改state的值为上面计算的新值,此处不需要CAS操作保证原子性,
            // 因为当前线程已经获取了锁,那其他线程就不能修改state,所以这里可以放心修改
            setState(nextc);
            return true;
        }
        // 若以上条件均不满足,表示有其他线程占用了锁,则直接返回false
        return false;
    }
}

  FairSyncThe implementation is relatively simple. It is worth noting that, because FairSync implements a fair lock, before a thread acquires a lock, it will first determine whether there is a thread that is trying to acquire the lock before it is queued. After those threads .


2.6 ReentrantLock member attributes and construction method

  After reading the inner class, let's take ReentrantLocka formal look at how it works. First , let's take a look at its member properties and constructor methods:

/** 记录使用的锁对象 */
private final Sync sync;

/** 默认构造方法,初始化锁对象,默认使用非公平锁 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/** 参数为boolean类型的构造方法,若为false,使用非公平锁,否则使用公平锁 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2.7 Locking and unlocking of ReentrantLock

  Let me take a look at ReentrantLockthe two most important operations, locking and unlocking.

(1) The method of acquiring the lock

/** 
 * 此方法让当前线程获取锁,若获取失败,线程将阻塞,直到获取成功为止 
 * 此方法不会响应中断,也就是在没有获取锁前,将无法退出
 */
public void lock() {
    // 直接调用锁对象的lock方法,也就是之前分析的内部类中的lock方法
    sync.lock();
}

/**
 * 此方法获取锁,和上面的方法类似,唯一的不同就是,调用这个方法获取锁时,
 * 若线程被阻塞,可以响应中断
 */
public void lockInterruptibly() throws InterruptedException {
    // 调用sync对象的acquireInterruptibly方法,这个方法定义在AQS中,
    // 也是AQS提供给子类的一个模板方法,内部也是通过tryAcquire获取锁,
    // 若获取失败,线程将被阻塞,但是此方法会检测中断信号,
    // 若检测到中断,将通过抛出异常的方式退出阻塞
    // 关于这个方法的具体实现,可以去参考AQS的相关博客,此处就不展开描述了
    sync.acquireInterruptibly(1);
}


/** 
 * 调用此方法尝试获取一次锁,不论成功失败,都会直接返回
 */
public boolean tryLock() {
    // 此处直接调用Sync类中的nonfairTryAcquire方法,
    // 这也就是为什么nonfairTryAcquire定义在父类Sync中,
    // 因为不论是使用公平锁还是非公平锁,都需要在此处调用这个方法
    return sync.nonfairTryAcquire(1);
}

(2) Whether the lock method is implemented

/**
 * 此方法用来释放锁
 */
public void unlock() {
    // 此处调用的是AQS的release方法,这个方法也是AQS提供的一个模板方法,
    // 在这个方法中,将调用子类重写的tryRelease方法尝试释放锁,若释放成功
    // 则会唤醒等待队列中的下一个线程,让它停止阻塞,开始尝试获取锁,
    // 关于这个方法的具体实现,可以参考我之前推荐的AQS源码分析博客。
    // 这里需要注意,传入的参数是1,表明释放锁实际上就是让state的值-1
    sync.release(1);
}

  The above is ReentrantLockthe method of locking and unlocking, which is unexpected and very simple. Each method has only one sentence and calls AQSthe template method provided in the class. This is AQSthe benefit, AQSencapsulating the thread synchronization code, we only need to use it in the class, we can easily implement a lock. That's why I said earlier that before reading ReentrantLock, you must study first AQSand understand AQS, ReentrantLockthere is no difficulty in understanding .

  The above ReentrantLockare the key methods in. In addition to these methods, there are many other methods, but those methods are not critical, and the implementation is very simple. Basically, it is just a sentence of code. You can read the source code yourself. I will not list them one by one.


3. Summary

  After the above analysis, we will find that ReentrantLockthe implementation principle is very simple, because it is based on AQSimplementation, the complexity is encapsulated in AQS, ReentrantLockonly its users, so learning is ReentrantLockactually learning AQS. AQSIt is Javaan important component in concurrency, and many classes are implemented based on it, such as very commonly used CountDownLatch. AQSIt is also a common question in the interview, so you must study it carefully. Here again I recommend the AQSanalysis blog I wrote : Concurrency-the realization principle of the abstract queue synchronizer AQS .


4. Reference

  • JDK1.8 source code

Guess you like

Origin www.cnblogs.com/tuyang1129/p/12689122.html