java.util.concurrent.locks包下的ReentrantLock类实现详解(一)

一、ReentrantLock 类介绍

我觉得学习Java源码一定要首先读读源码的注释文档。 

官方注释文档解释到,该类作为一个提供可重入功能的锁,实现了Lock接口,提供了和synchronized类似的功能,并且提供了额外的功能以实现同步。


二、ReentrantLock中的相关属性和方法

属性 含义
Sync sync Sync为ReentrantLock中的静态内部类,继承了AbstractQueuedSynchronized类,其中AbstractQueueSynchronized类提供了实现可重入锁最基本的功能
构造函数 含义
public ReentrantLock() 默认构造函数,内部构造出NonfairSync类,该类为Sync的子类,也是ReentrantLock中的内部类。主要实现非公平锁
public ReentrantLock(boolean fair) 传入参数指示创建公平锁实现类(FairSync)还是非公平锁(NonfairSync)
相关重要方法 含义
public lock() 内部调用sync.lock(),当前线程获取锁的方法,如果已经有线程获取了同一个ReentrantLock的锁,那么其他线程就会挂起等待,如果同一个线程试图再次获取锁,那么会将当前的state加1,后续会说到实现。
public boolean tryLock() 内部调用sync.nonfairTryAcquire(1),如果可以获取锁会设置状态并获取锁然后返回true,如果不能获取锁,那么会返回false。
public void unlock() 内部调用sync.release(1);,如果当前线程持有锁,那么将states相应递减,如果state减为0了,那么释放锁。如果当前线程没有持有锁,并且执行了该方法,那么会抛出IllegalMonitorStateException异常。

先说明这几个方法,下面将从NonfairSync出发说明lock()和unlock()的主要实现。

三、ReentrantLock内部类sync、NonfairSync的实现。

调用ReentrantLock的默认构造器获取一个可重入锁的对象,其内部其实是执行了sync = new NonfairSync();构造出一个非公平锁对象,那么NonfairSync类是怎么写的呢,下面看其源代码:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

这段源码可以看出来,其主要提供lock和tryAcquire方法。但是都是调用父类的方法。那么Sync怎么定义的呢,看看其源码:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock();

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
     */
    final boolean nonfairTryAcquire(int acquires) {
        。。。
    }

    protected final boolean tryRelease(int releases) {
        。。。
    }
    
}

部分代码被我删了,可以自行去看,我们可以看到其实Sync只是定义了规范供子类拓展,实现方法都在AbstractQueuedSynchronizer中。该类才是重中之重。

下面从获取锁开始一步步debug看看代码到底如何走的.有如下启动代码:

public class TestReentrantlock {

    private static final ReentrantLock lock = new ReentrantLock(false);

    static class T implements Runnable{

        private static long i = 0;

        public void run() {
            try {
                setAndGet(Thread.currentThread().getId());
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }

        void setAndGet(long a) throws Exception{
            lock.lock();
            try {

                System.out.println("thread-" + Thread.currentThread().getName());
                System.out.println("beafore i : " + i);
                i = a;
                System.out.println("after i : " + i);
            }
            catch (Exception e){
                e.printStackTrace();
            }

            finally {
                Thread.sleep(1000);
                lock.unlock();
            }
        }

    }
    public static void main(String[] args) {
        Thread t1 = new Thread(new T(),"t1-thread");
        Thread t2 = new Thread(new T(), "t2-thread");
        t1.start();
        t2.start();
    }
}

我这边启动两个线程,看看到底如何获取锁和释放锁的。

我用的是idea debug的,记得在断点处设置debug模式,如下:

java.util.concurrent.locks包下的ReentrantLock类实现详解(一)_第1张图片

java.util.concurrent.locks包下的ReentrantLock类实现详解(一)_第2张图片

这边可以选择具体要调试的线程,可以随便选择一个,现在两个都停在lock.lock()处了。我选择t1-thread进行调试把获取锁的实现过程走一遍,相关的方法如下图所示。

java.util.concurrent.locks包下的ReentrantLock类实现详解(一)_第3张图片

如上所示,在没有其他线程获取锁的状态下,走的相关方法。可以看出比较简单,其中unsafe.compareAndSwapInt(this,stateOffset,expect,update),就是著名的java提供的原生的原子操作。该方法的意思是:获取参数1,这里也就是this的偏移量(参数二),这里也就是stateOffset的内存存储的值,如果expect的值和该值相同,那么就将该值设置为update。这里需要设置的值是state,该值就表示当前锁的状态,在AbstractQueuedSynchronizer(AQS)中定义,其定义如下:private  volatile int state; 可以看出,该值用volatile声明了,代表该值的变化对其他线程是可见的。

该流程可以简单的描述为下面的方式:通过原子操作将state的值设置为1,代表当前线程获取了一个锁,然后设置线程为当前线程。

那么此时t1-thread已经获取了当前锁,此时用t2-thread再去获取锁是什么样的一个流程呢?下面开始调试:

其流程如下:

java.util.concurrent.locks包下的ReentrantLock类实现详解(一)_第4张图片

下面分析每一步的代码:

/*NonfairSync.lock()*/
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
这段代码首先判断state的值是不是0,因为现在有一个线程持有锁了,所以进入,acquire(1)。
/*分别执行了tryAcquire(1)再次尝试获取锁,如果还不能获取锁,调用addWaiter(Node.EXCLUSIVE)将当前线程添加进等待链表,acquireQueued会将当前线程再次申请锁,如果还未成功,则将当前线程挂起*/
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
/*addWaiter源码,首先创建当前线程的一个节点Node,tail存储的链表的当前节点,如果当前节点已经存在了,直接将线程节点插入到链表的下一个节点,并将当前节点置为线程节点,并返回节点
如果链表中还没有节点,那么调用enq(node)
*/
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

/*用一个for循环创建链表,并返回tail头节点*/
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//是否能获取锁
    try {
        boolean interrupted = false; //线程是否中断
        for (;;) {
            final Node p = node.predecessor(); //返回线程节点前一个节点
            if (p == head && tryAcquire(arg)) { //判断p是否为head节点,再次尝试获取锁
                setHead(node);
                p.next = null; // help GC 释放线程节点
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && 
                parkAndCheckInterrupt()) //shoudParkAfterFailedAcquire设置p节点的waitstatus为-1,parkAndCheckInterrupt()设置当前线程挂起。等待获取锁的线程释放锁资源。
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

那么挂起的线程什么时候恢复并且去再次获取锁呢?

当前持有锁的线程释放的时候,看下释放的调用过程:

具体的代码可以跟下源代码。

 

你可能感兴趣的:(Java)