前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >互联网JAVA面试常问问题(七)- 带你走入AQS同步器源码

互联网JAVA面试常问问题(七)- 带你走入AQS同步器源码

作者头像
程序员小强
发布2019-09-20 16:59:34
5670
发布2019-09-20 16:59:34
举报

周末叫醒我的不是忘记关掉的闹钟,而是一颗爱学习的红心~大家周日好,欢迎和小强一起继续研究互联网JAVA面试题~

上一篇文章中,小强给大家介绍了可重入锁的相关知识,通过学习ReentrantLock的源码能够看出,它继承了AbstractQueuedSynchronizer。所以,本文再简单的的介绍一下AQS的源码。

AbstractQueuedSynchronizer

队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。AQS的源码如下:

public abstract class AbstractQueuedSynchronizer
       extends AbstractOwnableSynchronizer
       implements java.io.Serializable {    
    protected AbstractQueuedSynchronizer() { }    
    static final class Node {
       ........
   }    
    private transient volatile Node head;    
    private transient volatile Node tail;    
    private volatile int state;    
    protected final int getState() {        
        return state;
   }    
    protected final void setState(int newState) {
       state = newState;
   }    
   protected final boolean compareAndSetState(int expect, int update) {        // See below for intrinsics setup to support this
       return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
   }

从源码可以看出,AQS内部使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,多线程争用资源被阻塞时会进入此队列,其中volatile修饰了以下变量,来保证了多线程之间的可见:

1.内部状态state

2.等待队列的头节点head

3.等待队列的尾节点head。

state的访问方式有三种:

1.getState()

2.setState()

3.compareAndSetState()

其中FIFO队列的实现如下,每个节点中,除了存储了当前线程,前后节点的引用以外,还有一个waitStatus变量,用于描述节点当前的状态(共四种状态:1.CANCELLED 取消状态;2.SIGNAL 等待触发状态;3.CONDITION 等待条件状态;4.PROPAGATE 状态需要向后传播 )

static final class Node {    
static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node();    
static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null;    
static final int CANCELLED =  1;    
static final int SIGNAL    = -1;    
static final int CONDITION = -2;    
static final int PROPAGATE = -3;    
volatile int waitStatus;    
volatile AbstractQueuedSynchronizer.Node prev;    
volatile AbstractQueuedSynchronizer.Node next;    
volatile Thread thread;AbstractQueuedSynchronizer.Node nextWaiter;    
final boolean isShared() {        
    return nextWaiter == SHARED;
}    
final AbstractQueuedSynchronizer.Node predecessor() throws NullPointerException {
   AbstractQueuedSynchronizer.Node p = prev;       
    if (p == null)            
        throw new NullPointerException();        
    else
       return p;
}
}

AQS的不同子类争用共享资源的方式也不同。AQS子类中,只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

在AQS中,有两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。接下来主要介绍Exclusive下是如何获取和释放锁的:

锁获取acquire(int)
 /**
    * Acquires in exclusive mode, ignoring interrupts.  Implemented
    * by invoking at least once {@link #tryAcquire},
    * returning on success.  Otherwise the thread is queued, possibly
    * repeatedly blocking and unblocking, invoking {@link
    * #tryAcquire} until success.  This method can be used
    * to implement method {@link Lock#lock}.
    *
    * @param arg the acquire argument.  This value is conveyed to
    *        {@link #tryAcquire} but is otherwise uninterpreted and
    *        can represent anything you like.
    */
   public final void acquire(int arg) {        
        if (!tryAcquire(arg) &&
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           selfInterrupt();
   }

所以在获取锁的过程中,我们看到了如下步骤

1.tryAcquire()尝试直接去获取资源,如果成功则直接返回;

2.addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;

3.acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。

4.如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

锁释放release(int)
    /**
    * Releases in exclusive mode.  Implemented by unblocking one or
    * more threads if {@link #tryRelease} returns true.
    * This method can be used to implement method {@link Lock#unlock}.
    *
    * @param arg the release argument.  This value is conveyed to
    *        {@link #tryRelease} but is otherwise uninterpreted and
    *        can represent anything you like.
    * @return the value returned from {@link #tryRelease}
    */
   public final boolean release(int arg) {        
        if (tryRelease(arg)) {
           Node h = head;            
            if (h != null && h.waitStatus != 0)
               unparkSuccessor(h);            
            return true;
       }        
        return false;
   }

此方法是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。它调用tryRelease()来释放资源。

小结

最后再回顾一下AQS的应用,以及ReentrantLock是如何实现的

ReentrantLock中state初始化为0,表示未锁定状态。当一个线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到此线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,这个线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MoziInnovations 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AbstractQueuedSynchronizer
  • 锁获取acquire(int)
  • 锁释放release(int)
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档