zoukankan      html  css  js  c++  java
  • Java并发包学习--ReentrantLock

    这个锁叫可重入锁。它其实语义上和synchronized差不多,但是添加了一些拓展的特性。

    A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.

    我们重点看看它的源代码实现。

    在ReentrantLock类中很大一部分的函数是委托给这个类Sync来完成的。

        /** Synchronizer providing all implementation mechanics */
        private final Sync sync;

    Sync继承了AbstractQueuedSynchronizer,声明了lock()的抽象函数,还实现了nonfairTryAcquire的抽象函数。可以看到它是non-fair锁的默认实现方式。

     1     /**
     2      * Base of synchronization control for this lock. Subclassed
     3      * into fair and nonfair versions below. Uses AQS state to
     4      * represent the number of holds on the lock.
     5      */
     6     static abstract class Sync extends AbstractQueuedSynchronizer {
     7         private static final long serialVersionUID = -5179523762034025860L;
     8 
     9         /**
    10          * Performs {@link Lock#lock}. The main reason for subclassing
    11          * is to allow fast path for nonfair version.
    12          */
    13         abstract void lock();
    14 
    15         /**
    16          * Performs non-fair tryLock.  tryAcquire is
    17          * implemented in subclasses, but both need nonfair
    18          * try for trylock method.
    19          */
    20         final boolean nonfairTryAcquire(int acquires) {
    21             final Thread current = Thread.currentThread();
    22             int c = getState();
    23             if (c == 0) {
    24                 if (compareAndSetState(0, acquires)) {
    25                     setExclusiveOwnerThread(current);
    26                     return true;
    27                 }
    28             }
    29             else if (current == getExclusiveOwnerThread()) {
    30                 int nextc = c + acquires;
    31                 if (nextc < 0) // overflow
    32                     throw new Error("Maximum lock count exceeded");
    33                 setState(nextc);
    34                 return true;
    35             }
    36             return false;
    37         }
    38 
    39         protected final boolean tryRelease(int releases) {
    40             int c = getState() - releases;
    41             if (Thread.currentThread() != getExclusiveOwnerThread())
    42                 throw new IllegalMonitorStateException();
    43             boolean free = false;
    44             if (c == 0) {
    45                 free = true;
    46                 setExclusiveOwnerThread(null);
    47             }
    48             setState(c);
    49             return free;
    50         }
    51 
    52         protected final boolean isHeldExclusively() {
    53             // While we must in general read state before owner,
    54             // we don't need to do so to check if current thread is owner
    55             return getExclusiveOwnerThread() == Thread.currentThread();
    56         }
    57 
    58         final ConditionObject newCondition() {
    59             return new ConditionObject();
    60         }
    61 
    62         // Methods relayed from outer class
    63 
    64         final Thread getOwner() {
    65             return getState() == 0 ? null : getExclusiveOwnerThread();
    66         }
    67 
    68         final int getHoldCount() {
    69             return isHeldExclusively() ? getState() : 0;
    70         }
    71 
    72         final boolean isLocked() {
    73             return getState() != 0;
    74         }
    75 
    76         /**
    77          * Reconstitutes this lock instance from a stream.
    78          * @param s the stream
    79          */
    80         private void readObject(java.io.ObjectInputStream s)
    81             throws java.io.IOException, ClassNotFoundException {
    82             s.defaultReadObject();
    83             setState(0); // reset to unlocked state
    84         }
    85     }
    View Code

    可以看出ReentrantLock是可重入锁的实现。而内部是委托java.util.concurrent.locks.ReentrantLock.Sync.lock()实现的。java.util.concurrent.locks.ReentrantLock.Sync是抽象类,有java.util.concurrent.locks.ReentrantLock.FairSync和java.util.concurrent.locks.ReentrantLock.NonfairSync两个实现,也就是常说的公平锁和不公平锁。

    公平锁和非公平锁

    如果获取一个锁是按照请求的顺序得到的,那么就是公平锁,否则就是非公平锁。

    在没有深入了解内部机制及实现之前,先了解下为什么会存在公平锁和非公平锁。公平锁保证一个阻塞的线程最终能够获得锁,因为是有序的,所以总是可以按照请求的顺序获得锁。不公平锁意味着后请求锁的线程可能在其前面排列的休眠线程恢复前拿到锁,这样就有可能提高并发的性能。这是因为通常情况下挂起的线程重新开始与它真正开始运行,二者之间会产生严重的延时。因此非公平锁就可以利用这段时间完成操作。这是非公平锁在某些时候比公平锁性能要好的原因之一。

    先看下公平锁FairSync的实现:

     1 /**
     2      * Sync object for fair locks
     3      */
     4     final static class FairSync extends Sync {
     5         private static final long serialVersionUID = -3000897897090466540L;
     6 
     7         final void lock() {
     8             acquire(1);   //调用的是AQS中的acquire()
     9         }
    10 
    11         /**
    12          * Fair version of tryAcquire.  Don't grant access unless
    13          * recursive call or no waiters or is first.
    14          */
    15         protected final boolean tryAcquire(int acquires) {
    16             final Thread current = Thread.currentThread();
    17             int c = getState();    //也是AQS中的函数,记录当前有多少个线程拿了锁,对于重入锁,state不一定为1
    18             if (c == 0) {
    19                 if (isFirst(current) &&  //判断是否是队列中的第一个,不是也拿不到,只能返回false,保证公平
    20                     compareAndSetState(0, acquires)) {  //如果是就设置为1,刚刚acquire为1
    21                     setExclusiveOwnerThread(current);   //并且设置当前线程为独占线程
    22                     return true;
    23                 }
    24             }  //即使当前state不为0,也不一定拿不到锁,只要是自己的线程独占了锁,就可以重进入
    25             else if (current == getExclusiveOwnerThread()) {
    26                 int nextc = c + acquires;   //acquires为1,所以每进入一次,就加1
    27                 if (nextc < 0)
    28                     throw new Error("Maximum lock count exceeded");
    29                 setState(nextc);
    30                 return true;
    31             }
    32             return false;  //不然就返回false,会执行后面的acquireQueued()函数
    33         }
    34     }

    lock的话就直接用了AbstractQueuedSynchronizer的acquire()函数,并且实现了一个公平锁版本的tryAcquire函数。

    我们可以看到acquire的参数是1,看看AbstractQueuedSynchronizer中acquire的实现:

     1     /**
     2      * Acquires in exclusive mode, ignoring interrupts.  Implemented
     3      * by invoking at least once {@link #tryAcquire},
     4      * returning on success.  Otherwise the thread is queued, possibly
     5      * repeatedly blocking and unblocking, invoking {@link
     6      * #tryAcquire} until success.  This method can be used
     7      * to implement method {@link Lock#lock}.
     8      *
     9      * @param arg the acquire argument.  This value is conveyed to
    10      *        {@link #tryAcquire} but is otherwise uninterpreted and
    11      *        can represent anything you like.
    12      */
    13     public final void acquire(int arg) {
    14         if (!tryAcquire(arg) &&      //tryAcquire(1)是在FairSync中自己实现的。
    15             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  //被中断的话,acquireQueued会返回true,否则false
    16             selfInterrupt();
    17     }

    我们先看看AbstractQueuedSynchronizer的tryAcquire(1),奇怪这里居然是直接抛出了异常。

     1     /**
     2      * Attempts to acquire in exclusive mode. This method should query
     3      * if the state of the object permits it to be acquired in the
     4      * exclusive mode, and if so to acquire it.
     5      *
     6      * <p>This method is always invoked by the thread performing
     7      * acquire.  If this method reports failure, the acquire method
     8      * may queue the thread, if it is not already queued, until it is
     9      * signalled by a release from some other thread. This can be used
    10      * to implement method {@link Lock#tryLock()}.
    11      *
    12      * <p>The default
    13      * implementation throws {@link UnsupportedOperationException}.
    14      *
    15      * @param arg the acquire argument. This value is always the one
    16      *        passed to an acquire method, or is the value saved on entry
    17      *        to a condition wait.  The value is otherwise uninterpreted
    18      *        and can represent anything you like.
    19      * @return {@code true} if successful. Upon success, this object has
    20      *         been acquired.
    21      * @throws IllegalMonitorStateException if acquiring would place this
    22      *         synchronizer in an illegal state. This exception must be
    23      *         thrown in a consistent fashion for synchronization to work
    24      *         correctly.
    25      * @throws UnsupportedOperationException if exclusive mode is not supported
    26      */
    27     protected boolean tryAcquire(int arg) {
    28         throw new UnsupportedOperationException();
    29     }
    View Code

    其实不然,这里是一个protected的函数,利用了java的多态,在ReentrantLock中,有两个构造函数,默认是非公平锁NonfairSync,所以实际上对于公平锁而言,是调用了FairSync中自己的实现(代码在上面)。

     1     /**
     2      * Creates an instance of {@code ReentrantLock}.
     3      * This is equivalent to using {@code ReentrantLock(false)}.
     4      */
     5     public ReentrantLock() {
     6         sync = new NonfairSync();
     7     }
     8 
     9     /**
    10      * Creates an instance of {@code ReentrantLock} with the
    11      * given fairness policy.
    12      *
    13      * @param fair {@code true} if this lock should use a fair ordering policy
    14      */
    15     public ReentrantLock(boolean fair) {
    16         sync = (fair)? new FairSync() : new NonfairSync();
    17     }
    View Code

    前面说明对于AQS存在一个state来描述当前有多少线程持有锁。由于AQS支持共享锁(例如读写锁,后面会继续讲),所以这里state>=0,但是由于ReentrantLock是独占锁,所以这里不妨理解为0<=state,acquires=1。isFirst(current)是一个很复杂的逻辑,包括踢出无用的节点等复杂过程,这里暂且不提,大体上的意思是说判断AQS是否为空或者当前线程是否在队列头(为了区分公平与非公平锁)。

    可以看到,在tryAcquire中,调用了c = getState()来判断当前有多少线程拿到了锁,

    1. 如果c ==0,说明这时锁没有人用。但是还要调用isFirst判断一下该线程是不是AQS队列的第一个。
      • 如果第一个,那么就通过compareAndSetState把当前state设置为1(即acquires)。然后通过setExclusiveOwnerThread设置当前线程为这个锁的独占线程。
      • 如果不是第一个,那么就不好意思,只能返回false,回去排队了(acquireQueued())。这就是公平锁和不公平锁的区别,公平锁必须永远保证队列的第一个拿到锁。所以这时即使没人拿到锁,那轮不到你,需要排队。
    2. 如果c !=0,也不一定拿不到锁,因为是可重入锁。那么调用getExclusiveOwnerThread()来判断当前这个线程是不是锁的独占线程。
      • 如果这个锁的独占线程也是它,那么就把锁当前的数目加上1(即acquires)。这里之所以不是将当前状态位state设置为1,而是修改为旧值+1呢?这是因为ReentrantLock是可重入锁,同一个线程每持有一次state就+1。
      • 如果不是,那就不好意思,那就只能返回false,也回去排队了(acquireQueued())。

    setExclusiveOwnerThread,这个函数在AbstractOwnableSynchronizer类中,而AbstractQueuedSynchronizer又继承了他,这个变量就是用来控制说只有一个线程能访问这个锁,也就是这个独占锁所对应的独占线程。

     1 public abstract class AbstractOwnableSynchronizer
     2     implements java.io.Serializable {
     3 
     4     /** Use serial ID even though all fields transient. */
     5     private static final long serialVersionUID = 3737899427754241961L;
     6 
     7     /**
     8      * Empty constructor for use by subclasses.
     9      */
    10     protected AbstractOwnableSynchronizer() { }
    11 
    12     /**
    13      * The current owner of exclusive mode synchronization.
    14      */
    15     private transient Thread exclusiveOwnerThread;
    16 
    17     /**
    18      * Sets the thread that currently owns exclusive access. A
    19      * <tt>null</tt> argument indicates that no thread owns access.
    20      * This method does not otherwise impose any synchronization or
    21      * <tt>volatile</tt> field accesses.
    22      */
    23     protected final void setExclusiveOwnerThread(Thread t) {
    24         exclusiveOwnerThread = t;
    25     }
    26 
    27     /**
    28      * Returns the thread last set by
    29      * <tt>setExclusiveOwnerThread</tt>, or <tt>null</tt> if never
    30      * set.  This method does not otherwise impose any synchronization
    31      * or <tt>volatile</tt> field accesses.
    32      * @return the owner thread
    33      */
    34     protected final Thread getExclusiveOwnerThread() {
    35         return exclusiveOwnerThread;
    36     }
    37 }
    View Code

    所以总结一下,FairSync版本的tryAcquire的功能就是保证如果是队列中的第一个的话就能够拿到锁,并且提供了可重入功能,即同一个线程获取这个锁多一次,state计数就加1。

    接下来继续分析acquire()中的语句:

    1.  如果tryAcquire(1)成功,即(!tryAcquire(1))==false,由于&&的短路功能,那么就直接返回了,成功拿到锁。
    2.  如果tryAcquire(1)返回false,就意味要排队了。那么就会执行acquireQueued(addWaiter(Node.EXCLUSIVE),1),加入一个独占的Node。addWaiter(Node mode)函数的功能就是在AbstractQueuedSynchronizer中维护的双向队列的尾部加入一个节点。AQS支持独占锁和共享锁,而独占锁在Node中就意味着条件(Condition)队列为空。它调用Node中对应的构造函数,把这个node的nextWaiter设置为Node.EXCLUSIVE,即为null。addWaiter()函数返回的是这个新加入的node,并且把这个传给了acquireQueued函数。

    我们只要记住,addWaiter会在AQS的双向队列的尾部加入一个新的Node,表示当前线程排队等待锁。

     1     /**
     2      * Creates and enqueues node for given thread and mode.
     3      *
     4      * @param current the thread
     5      * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     6      * @return the new node
     7      */
     8     private Node addWaiter(Node mode) {  //这里的mode是Node.EXCLUSIVE
     9         Node node = new Node(Thread.currentThread(), mode);
    10         // Try the fast path of enq; backup to full enq on failure
    11         Node pred = tail;
    12         if (pred != null) {   //判断tail是否为null
    13             node.prev = pred;
    14             if (compareAndSetTail(pred, node)) {
    15                 pred.next = node;
    16                 return node;
    17             }
    18         }
    19         enq(node);  //enq函数会创建一个新的head,并且把node加入到tail。
    20         return node;
    21     }

    Node的nextWaiter有两种值,一种是SHARED(一个new Node),一种是EXCLUSIVE(null),分别用来表示共享锁和互斥锁。

    1         /** Marker to indicate a node is waiting in shared mode */
    2         static final Node SHARED = new Node();
    3         /** Marker to indicate a node is waiting in exclusive mode */
    4         static final Node EXCLUSIVE = null;
    1         Node(Thread thread, Node mode) {     // Used by addWaiter
    2             this.nextWaiter = mode;  //对于SHARED,这是一个node,对于ECLUSIVE,这是null
    3             this.thread = thread;
    4         }

    再看看AbstractQueuedSynchronizer中的acquireQueued(),

    引用一下别人说的

    acquireQueued过程是这样的:

    1. 如果当前节点是AQS队列的头结点(如果第一个节点是DUMP节点也就是傀儡节点,那么第二个节点实际上就是头结点了),就尝试在此获取锁tryAcquire(arg)。如果成功就将头结点设置为当前节点(不管第一个结点是否是DUMP节点),返回中断位。否则进行2。
    2. 检测当前节点是否应该park(),如果应该park()就挂起当前线程并且返回当前线程中断位。进行操作1。

    这里看到,其实acquireQueued中有一个for循环,所以它是没那么快返回的。引用一下:

    自旋请求锁,如果可能的话挂起线程,直到得到锁,返回当前线程是否中断过(如果park()过并且中断过的话有一个interrupted中断位)。

     1     /**
     2      * Acquires in exclusive uninterruptible mode for thread already in
     3      * queue. Used by condition wait methods as well as acquire.
     4      *
     5      * @param node the node
     6      * @param arg the acquire argument
     7      * @return {@code true} if interrupted while waiting
     8      */
     9     final boolean acquireQueued(final Node node, int arg) {
    10         try {
    11             boolean interrupted = false;
    12             for (;;) {  //一直循环,直到拿到锁
    13                 final Node p = node.predecessor();  //获取前一个node,如果为null,会抛出NullPointerException
    14                 if (p == head && tryAcquire(arg)) {  //前面是head,那么再tryAcquire一下
    15                     setHead(node);    //成功则设置当前节点为head,它会把thread和prev域设置为null
    16                     p.next = null; // help GC    //此时原来的head就可以GC了
    17                     return interrupted;    //成功则返回,如果此时被中断了,会调用selfInterrupt()
    18                 }  //如果获取失败,才要判断是否要park了
    19                 if (shouldParkAfterFailedAcquire(p, node) &&  
    20                     parkAndCheckInterrupt())  //需要park才执行parkAndCheckInterrupt()
    21                     interrupted = true;   //如果已经被interrupt,设置一下interrupted
    22             }
    23         } catch (RuntimeException ex) {
    24             cancelAcquire(node);
    25             throw ex;
    26         }
    27     }

     可以顺便看下setHead()函数,它会把head的thread和prev设置为null,并用head指向它。

     1     /**
     2      * Sets head of queue to be node, thus dequeuing. Called only by
     3      * acquire methods.  Also nulls out unused fields for sake of GC
     4      * and to suppress unnecessary signals and traversals.
     5      *
     6      * @param node the node
     7      */
     8     private void setHead(Node node) {
     9         head = node;
    10         node.thread = null;
    11         node.prev = null;
    12     }
    View Code

    如果前一个节点不是head,并且tryAcquire失败,那么就要调用shouldParkAfterFailedAcquire了。park的意思大概就是挂起一个线程,具体的语义参考LockSupport这个类。

    要理解shouldParkAfterFailedAcquire,首先要理解Node中定义的四个状态位是什么意思:

     1         /** waitStatus value to indicate thread has cancelled */
     2         static final int CANCELLED =  1;//只有这个大于0,意味着Node对应的线程超时或中断,也就是我不要锁了,忽略我吧
     3         /** waitStatus value to indicate successor's thread needs unparking */
     4         static final int SIGNAL    = -1;   //就说当前node的thread释放锁的时候,要记得唤醒下一个node
     5         /** waitStatus value to indicate thread is waiting on condition */
     6         static final int CONDITION = -2;   //暂时用不到
     7         /**
     8          * waitStatus value to indicate the next acquireShared should
     9          * unconditionally propagate
    10          */
    11         static final int PROPAGATE = -3; //暂时用不到,ReentrantLock是EXCLUSIVE

    分为3种情况:

    1. 如果前面节点是SIGNAL,那么直接返回true。意思是前面释放的时候会唤醒你的,你可以park了。
    2. 如果前面节点是CANCELLED,那么把所有waitStatus为CANCELLED的都去掉,然后返回false。
    3. 如果是0或PROPAGATE,那么调用compareAndSetWaitStatus,把前面的node的状态设置为SIGNAL,意思是前面线程释放的时候,要记得唤醒下一个node啊,我一直在等待啊。同样返回false,不park。
     1     /**
     2      * Checks and updates status for a node that failed to acquire.
     3      * Returns true if thread should block. This is the main signal
     4      * control in all acquire loops.  Requires that pred == node.prev
     5      *
     6      * @param pred node's predecessor holding status
     7      * @param node the node
     8      * @return {@code true} if thread should block
     9      */
    10     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    11         int ws = pred.waitStatus;   //获取前面节点的状态
    12         if (ws == Node.SIGNAL)  //说明前一个node已经释放啦,可以唤醒下一个了
    13             /*
    14              * This node has already set status asking a release
    15              * to signal it, so it can safely park
    16              */
    17             return true;
    18         if (ws > 0) {  //大于0表示我不要锁了,所以用while循环一直往前找到
    19             /*
    20              * Predecessor was cancelled. Skip over predecessors and
    21              * indicate retry.
    22              */
    23         do {
    24         node.prev = pred = pred.prev;  //pred和当前的node.prev都指向pred.prev
    25         } while (pred.waitStatus > 0);  //直到找到小于或等于0的
    26         pred.next = node;
    27         } else {
    28             /*    //这里说吧前面node的状态改为SIGNAL,但是依旧不park
    29              * waitStatus must be 0 or PROPAGATE. Indicate that we
    30              * need a signal, but don't park yet. Caller will need to
    31              * retry to make sure it cannot acquire before parking. 
    32              */
    33             compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    34         } 
    35         return false;
    36     }

     再parkAndCheckInterrupt(),比较简单,依赖于LockSupport来挂起一个线程。

    1     /**
    2      * Convenience method to park and then check if interrupted
    3      *
    4      * @return {@code true} if interrupted
    5      */
    6     private final boolean parkAndCheckInterrupt() {
    7         LockSupport.park(this);
    8         return Thread.interrupted();
    9     }
    View Code

    记得前面如果acquire函数中,如果tryAcquire()为false,acquireQueued()为真(被中断的情况下返回true,否则返回false),那么就调用selfInterrupt(),把自己中断

    1     /**
    2      * Convenience method to interrupt current thread.
    3      */
    4     private static void selfInterrupt() {
    5         Thread.currentThread().interrupt();
    6     }
    View Code

    再来看看NonFairSync和FairSync的区别:

     1     /**
     2      * Sync object for non-fair locks
     3      */
     4     final static class NonfairSync extends Sync {
     5         private static final long serialVersionUID = 7316153563782823691L;
     6 
     7         /**
     8          * Performs lock.  Try immediate barge, backing up to normal
     9          * acquire on failure.
    10          */
    11         final void lock() {
    12             if (compareAndSetState(0, 1))    //差别就在这里,如果发现锁暂时没有人用,那么就立刻占为己有
    13                 setExclusiveOwnerThread(Thread.currentThread()); //并且设置互斥线程
    14             else
    15                 acquire(1);   //否则才是acquire,acquire()函数中又会调用tryAcquire()和acquireQueued()
    16         }
    17 
    18         protected final boolean tryAcquire(int acquires) {
    19             return nonfairTryAcquire(acquires);  //调用nonfairTryAcquire(),sync中是自己实现tryAcquire()
    20         }
    21     }

    看一下Sync中nonfairTryAcquire的实现,这是默认的实现:

     1         /**
     2          * Performs non-fair tryLock.  tryAcquire is
     3          * implemented in subclasses, but both need nonfair
     4          * try for trylock method.
     5          */
     6         final boolean nonfairTryAcquire(int acquires) {
     7             final Thread current = Thread.currentThread();
     8             int c = getState();
     9             if (c == 0) {
    10                 if (compareAndSetState(0, acquires)) {
    11                     setExclusiveOwnerThread(current);
    12                     return true;
    13                 }
    14             }
    15             else if (current == getExclusiveOwnerThread()) {
    16                 int nextc = c + acquires;
    17                 if (nextc < 0) // overflow
    18                     throw new Error("Maximum lock count exceeded");
    19                 setState(nextc);
    20                 return true;
    21             }
    22             return false;
    23         }
    View Code

    其实差别非常小,我们前面也说到,

    在fair情况下,采用的是:if (isFirst(current) && compareAndSetState(0, acquires))

    而在nonFair情况下,采用的是:  if (compareAndSetState(0, acquires))

    也就是说在nonFair情况下,我并不需要保证这个线程是队列的第一个线程才可以,其实就是那些还没有排队但是刚好要锁的线程,有可能比那些还在AQS队列的中线程还快速拿到锁。所以是不公平的,但是效率也明显有所提升。

    参考资料:

    1. 深入Java Concurrency
  • 相关阅读:
    Delphi中WebBrowser自动填表模板
    对TMemoryStream的一些改进(用到了LockFile)
    用Delphi画圆角Panel的方法(使用CreateRoundRectRgn创造区域,SetWindowRgn显示指定区域)
    Delphi5的System.pas只有11514行
    《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统
    Delphi动态申请数组内存的方法(不使用SetLength,采用和C相似的方式)
    Delphi的类型转换 good
    New 和 GetMem 的不同之处
    XML SelectSingleNode的使用 根据节点属性获取该节点
    ADO面板上的控件简介
  • 原文地址:https://www.cnblogs.com/longshaohang/p/3350261.html
Copyright © 2011-2022 走看看