记录一下ReentrantLock加锁和释放锁的核心代码

文章目录

    • 前言
    • 类关系
    • AQS的核心设计
    • 代码分析
      • 加锁
      • 释放锁
      • 公平锁与非公平锁到底区别在哪

前言

本文主要是在记录AQS相关知识点的时候,想起来一直消化的都是别人的文章,但是从来没有系统的看过源码分析,也没有自己去看到相关源码,于是就简单去看了下加锁和释放锁的核心实现,这里仅供记录,毕竟只看了半天,大佬轻喷。

类关系

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
    }
    
	static final class FairSync extends Sync {
	    private static final long serialVersionUID = -3000897897090466540L;
	
	    final void lock() {
	        acquire(1);
	    }
	}
	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);
	    }
	}
}

ReentrantLock实现Lock接口去实现加锁和释放锁的具体实现
核心功能通过内部类Sync对象实现,而Sync这个类继承AbstractQueuedSynchronizer,这个即使大名鼎鼎的AQS,
ReentrantLock的公平锁和非公平锁通过两个静态内部类FairSyncNonfairSync再去继承Sync重写获取锁的方法,
这是由于AQS对尝试获取锁和释放锁的逻辑是预留给子类去重写的,父类中的原方法会直接抛出异常

// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryRelease
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

AQS的核心设计

java.util.concurrent.locks.AbstractQueuedSynchronizer

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
     /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;
 
    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;
 
    /**
     * The synchronization state.
     */
    private volatile int state;  

	static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread
    }
 } 

AbstractQueuedSynchronizer继承java.util.concurrent.locks.AbstractOwnableSynchronizer

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
 
    /** Use serial ID even though all fields transient. */
    private static final long serialVersionUID = 3737899427754241961L;
 
    /**
     * Empty constructor for use by subclasses.
     */
    protected AbstractOwnableSynchronizer() { }
 
    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;
}

上述类的关键点

  • 核心变量使用volatile修饰 保证内存可见性
  • 核心变量使用CAS修改
  • AbstractQueuedSynchronizer 使用volatile维护获取对象锁的等待队列,通过链表实现维护头节点head和尾节点tail,每个要获取对象锁的线程都会被包装成java.util.concurrent.locks.AbstractQueuedSynchronizer.Node对象
  • AbstractQueuedSynchronizer 使用volatile修饰int变量state, 用来记录同步锁的状态, 0代表未被线程持有,大于0代表对象锁已被线程持有,因为支持重入锁这个int的值即为被同一个线程持有锁的次数,同一个线程多次获取对象锁递增1,释放锁递减1,直到值为0则锁完成释放
  • AbstractQueuedSynchronizer 继承AbstractOwnableSynchronizer 使用Thread类型的变量exclusiveOwnerThread记录获取到对象锁的线程,这个变量没有使用volatile修饰,是因为这个变量赋值的前提是必须先获取到对象锁,而获取到对象锁已经决定了这个操作不会存在并发问题了
  • 通过java.util.concurrent.locks.AbstractQueuedSynchronizer.Node#waitStatus节点的状态来决定线程在获取不到同步锁时是否需要调用LockSupport的park沉睡以及当调用释放锁的方法释放成功后调用LockSupport的unpark方法来唤醒线程,让沉睡的线程重新参与竞争

代码分析

我们主要先以非公平锁为切入点,ReentrantLock默认就是非公平锁,可以通过构造函数指定为公平锁。

加锁

看下Lock接口的一个实现类ReentrantLock

我们来看一下加锁的方法,非公平锁通过ReentrantLock的内部类NonfairSync实现,如果获取锁成功,记录获取锁的线程

// java.util.concurrent.locks.ReentrantLock.NonfairSync#lock  
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() {
        // 通过cas修改state的值来尝试加锁
        if (compareAndSetState(0, 1))
            // 获取到锁之后记录当前加锁线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 获取不到锁的逻辑
            acquire(1);
    }
 
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

看下关键的代码compareAndSetState调用的还是AQS的代码

// java.util.concurrent.locks.AbstractQueuedSynchronizer#compareAndSetState
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

通过CAS修改state的值如果为0则代表没有线程持有锁,然后修改为1,代表当前当前对象锁已经被持有,其它线程再竞争都要进入等待队列中。注意这个state的值不一定只有0和1。如果加锁成功,则将当前线程赋值给AbstractOwnableSynchronizerexclusiveOwnerThread属性,用来记录当前持有对象锁的线程。

获取锁的方法非常简单,复杂的在如果没有获取到的处理。我们再看下上面的NonfairSynclock方法,如果没有加锁成功,则会调用acquire方法

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

父类的tryAcquire方法会抛出异常,所以是留给子类实现的。还是来看非公平锁的实现,默认是ReentrantLock的内部类Sync,这里会再次尝试去获取一次锁

 
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果锁没有被任何线程持有
    if (c == 0) {
        // 通过CAS去加锁
        if (compareAndSetState(0, acquires)) {
            // 获取成功记录加锁线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    /**
     * 如果锁已经被线程占有,但是持有锁的线程是当前线程(这里就可以看出来ReentrantLock支持可重入锁),则 
     * 获取锁成功,且对锁的状态字段进行累加,相对的释放的时候也是递减释放的
     */
    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;
}

先获取到当前要获取锁的线程,然后再次判断对象锁是否被线程持有,如果没有持有,则直接加锁成功。

加锁成功记录加锁的线程。

如果还是加锁失败,则判断当前线程是否是正在持有对象锁的线程,如果是的话,则state持有次数累加1(1是传过来的值),然后将state再次刷回主存(volatile修饰),然后还是加锁成功。从这里就可以看出来ReentrantLock支持可重入锁。相对的当释放锁的时候,如果多个同步方法所需要的同步锁对象是同一个,加锁的时候每进入一次同步方法,则state累加1,而当每个同步方法执行结束的时候则依次递减1,而不是直接将锁完全释放。

tryAcquire如果还是没有获取到锁,则将当前线程包装成java.util.concurrent.locks.AbstractQueuedSynchronizer.Node对象加入到等待队列,等待队列是一个链表,维护了头部和尾部,采用了尾插法.代码在AbstractQueuedSynchronizer

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 
// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
private Node addWaiter(Node mode) {
    // 包装Node节点
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    /**
     * 判断当前的等待队列尾部是否为空,如果不为空(说明之前已经完成过链表的初始化工作),则快速将之前的队
     * 列尾部的next指向当前线程,当前线程再指向尾部节点,然后返回,即完成等待队列的插入
     */
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
	// 初始化链表,详解在下面
    enq(node);
    return node;
}
 
/**
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#enq
 * 下面这段代码当时看了很多遍,忽略了循环,导致很多地方对不起来非常重要,要仔细看
 * 第一次调用上面的addWaiter方法的时候,tail和head都为空,所以一定会进入到enq方法
 * 所以当程序开始执行的时候,一定是进入t==null这个判断的,然后就是初始化一个空的Node,new Node(),
 * 然后tail=head=new Node()。这里看的时候联系到后面好多地方取node的prev发现根本和程序对不上,但却忽
 * 略了当前方法是个循环,这一步执行结束并没有终结循环,所以程序还会再次进入的。
 * 第二次进入之后,t的值就是第一次循环赋值的空node,但不在是null了,然后进入下面的else分支, 于是变成
 * 当前Node的prev(其实就是head)是第一次循环的空node,然后tail被重新赋值为当前node,head的next指向
 * 当前节点,head保持不变, 关系如下
 * head ===> new Node()
 * head.next = 当前线程node = tail
 * 当前线程node.prev = head
 */
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;
            }
        }
    }
}
 
 
 
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
// 上面的addWaiter是把当前线程维护到等待队列的尾部,返回node,然后调用当前方法
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取当前节点的prev节点,如果为空,则抛出空指针异常
            // java.util.concurrent.locks.AbstractQueuedSynchronizer#enq会保证不为空
            final Node p = node.predecessor();
           /** 
           	* 如果当前节点的prev == head, 则说明当前节点是链表的第二个节点
           	* 1. java.util.concurrent.locks.AbstractQueuedSynchronizer#enq这个方法决定了   
           	*    第一个没有获取到锁的线程节点head此时是一个空节点,然后实际上存储第一个等待的线程就是	
           	*    head.next,然后等锁释放后,正好第一个线程的prev=head,也就是要唤醒的线程,唤醒后	
           	*    去获取锁,如果成功将当前节点指向head,作为目前唯一一个有效的等待线程节点获取到锁之		
           	*    后,其实整个等待队列就没有意义了,因此将head=当前线程节点(获取到锁的), 			
           	*    p.next = null,其实就是head.next为空了,也就是等待队列空了
			* 2. 当前node是addWaiter()方法执行完成后将Node传递进来的,只有第一个等待线程节点的.prev			 
			*    是head,所以这个判断只有创建好第一个等待线程节点的时候才会执行
            */
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            /** 
             * 有一个字段waitStatus,描述了如果当前线程到这里还没获取到对象锁,就要将对象设置为SIGNAL	
             * 即等待状态,然后等待持有对象锁的线程对锁对象执行unpark释放锁,否则这里的线程就要沉睡,
             * 沉睡需要等待释放锁之后的唤醒,由于这里是个死循环,唤醒之后继续去竞争所
             */        
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
 

看下线程获取不到锁对waitStatus值的处理

// java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
        return true;
    if (ws > 0) {
        /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
 
// 如果线程需要执行唤醒操作,则调用LockSupport.park方法先将当前线程沉睡
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

附录waitStatus的值含义java.util.concurrent.locks.AbstractQueuedSynchronizer.Node

 
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
static final int PROPAGATE = -3;

释放锁

释放锁直接调用的就是java.util.concurrent.locks.ReentrantLock#unlock

方法内部调用的是java.util.concurrent.locks.AbstractQueuedSynchronizer#release

tryRelease父类默认没有实现抛出的异常,所以我们还是来看非公平锁的实现java.util.concurrent.locks.ReentrantLock.Sync#tryRelease

// `java.util.concurrent.locks.ReentrantLock#unlock`
public void unlock() {
    // 每次只将记录同步锁的state的值递减1
    sync.release(1);
}
 
// `java.util.concurrent.locks.AbstractQueuedSynchronizer#release`
public final boolean release(int arg) {
    // 释放锁
    if (tryRelease(arg)) {
        Node h = head;
        // 如果头节点不为空则说明当前等待队列有线程等待,需要执行唤醒操作
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
 
// `java.util.concurrent.locks.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;
}

将对象锁的同步状态state的值递减1,这是由于ReentrantLock对重入锁的支持,同一个线程执行多个需要同一个对象锁的代码时,多次获取锁的时候会将同步状态state累加,所以释放的时候可以理解为其中某一个同步代码块/方法执行完毕,然后释放本代码块/方法的锁,所以要依次递减释放,而不能一下子全部释放掉。

然后判断当前线程是否是持有对象锁的线程,如果不是,则不允许执行释放操作。

而当同步状态最后一次释放到为0的时候,则说明此时对象锁不再被任何线程持有,需要清空保存持有对象锁线程的变量exclusiveOwnerThread,最后将state的最新值写回内存。

tryRelease方法执行完成之后,则释放了对象锁。之前我们看加锁过程中的时候是有一批获取不到对象锁的线程的waitStatus被标记为了SINAL状态的,即线程执行了park即等待操作,现在对象锁已经释放,需要对之前要获取对象锁的线程执行唤醒操作。判断当前等待队列的head是否为空以及是否需要执行唤醒操作,如果满足条件的话执行唤醒操作。

看下上面tryRelease方法执行成功后的unparkSuccessor方法

// java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor  
private void unparkSuccessor(Node node) {
    /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
 
    /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

再贴一遍waitStatus值的含义,我们目前看的加锁释放锁只牵扯到了SIGNAL,前面加锁的时候已经解释过。

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
static final int PROPAGATE = -3;

unparkSuccessor方法传入的Node是头节点, 判断要唤醒的线程的waitStatus如果小于0,则需要重置修改为0。

s == null条件成立的话,为什么还要倒序遍历呢?有点没看懂,不是双向链表吗?

判断头节点是否存在next节点,以及如果存在next节点状态是否是CANCELLED(从上面状态含义看大于0的只有这一个),如果满足条件,则从尾部开始向前遍历,找到最后一个waitStatus<=0的节点,然后唤醒这个线程。

这它喵的是个啥意思??就唤醒一个最初插入的节点,咋这么像公平锁呢??

公平锁与非公平锁到底区别在哪

这里理解一下,感觉公平不公平还得看同行衬托

非公平锁的lock方法

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);
    }
}

公平锁的lock方法

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

    final void lock() {
        acquire(1);
    }
}

这就看出来区别了,非公平锁在加锁的时候,会让给当前线程一次机会直接参与对state的竞争修改,如果能够竞争成功,则不需要进入等待队列

而公平锁则少了非公平锁的第一步,只能老老实实的直接执行acquire方法

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

之前已经说过了java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire方法是留给父类重写的,现在要看下公平锁的实现了

// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
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;
}

// 对比非公平锁,在c==0获取锁的时候多了一个方法hasQueuedPredecessors()
// java.util.concurrent.locks.AbstractQueuedSynchronizer#hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

这里就看到了公平锁与非公平锁在当前锁处于空闲的时候,多执行了一个方法hasQueuedPredecessors
哎,智商捉急,这个方法返回取反,判断在各种反向判断并且或者一大堆的就脑袋瓜子嗡嗡的。

这他么这点代码真是让我绕道沟里去了,比看前面代码还费劲。

这里还是去搜索了一下有没有人有相关的疑惑,搜到了这篇文章

请的外援

hasQueuedPredecessors条件不成立则执行获取锁操作,

h !=t 条件不成立则是h == t,

tail==head条件成立的情况下是等待队列里只有一个线程节点,且这个节点目前获取到了锁,具体看java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued之前分析的代码。

下面的判断就看不懂了,交给上面搜索的文章

你可能感兴趣的:(JAVA)