AbstractQueuedSynchronizer/AQS 使用拓展分析-优

来源:互联网 发布:魔兽插件mac版 编辑:程序博客网 时间:2024/06/05 06:36

 

java.util.concurrent.locks.AbstractQueuedSynchronizer是J.U.C里最核心,也是最复杂的一个基础的类,简称AQS。AbstractQueuedSynchronizer是CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AbstractQueuedSynchronizer是Lock/Executor实现的前提。

通过以下三个维度来实现:

第一个维度:原子性操作同步器的状态位

这里使用一个32位的整数来描述状态位,(AbstractQueuedSynchronizer类中有个属性叫:private volatile int state;
),在这里依然使用CAS操作来解决这个问题。事实上这里还有一个64位版本的同步器(AbstractQueuedLongSynchronizer,里面是long state

第二个维度:阻塞和唤醒线程

JDK1.5之前使用Thread.suspend和Thread.resume实现,属于过时的api了,也有可能发生死锁。Jdk1.5之后使用LockSupport类来实现。AQS: AbstractQueuedSynchronizer,就是通过调用 LockSupport .park() LockSupport .unpark()实现线程的阻塞和唤醒 的。提供的方法比如:

LockSupport.park()
LockSupport.park(Object)
LockSupport.parkNanos(Object,long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(Object,long)
LockSupport.parkUntil(long)
LockSupport.unpark(Thread)

LockSupportJDK中比较底层的类,是用来创建锁和其他同步工具类的基本线程阻塞原语。LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继  执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。 

public static void main(String[] args)
{
     LockSupport.park();
     System.out.println("block.");
}

运行该代码,可以发现主线程一直处于阻塞状态。因为 许可默认是被占用的 ,调用park()时获取不到许可,所以进入阻塞状态。

如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。

public static void main(String[] args)
{
     Thread thread = Thread.currentThread();
     LockSupport.unpark(thread);//释放许可
     LockSupport.park();// 获取许可
     System.out.println("b");
}

LockSupport是可不重入 的,如果一个线程连续2次调用 LockSupport .park(),那么该线程一定会一直阻塞下去。

public static void main(String[] args) throws Exception
{
  Thread thread = Thread.currentThread();
  
  LockSupport.unpark(thread);
  
  System.out.println("a");
  LockSupport.park();
  System.out.println("b");
  LockSupport.park();
  System.out.println("c");
}

这段代码打印出ab,不会打印c,因为第二次调用park的时候,线程无法获取许可出现死锁。

LockSupport可以响应中断性

public static void t2() throws Exception
{
  Thread t = new Thread(new Runnable()
  {
    private int count = 0;
 
    @Override
    public void run()
    {
      long start = System.currentTimeMillis();
      long end = 0;
      while ((end - start) <= 1000)
      {
        count++;
        end = System.currentTimeMillis();
      }
      System.out.println("after 1 second.count=" + count);
    //等待或许许可
      LockSupport.park();
      System.out.println("thread over." + Thread.currentThread().isInterrupted());
    }
  });
 
  t.start();
  Thread.sleep(2000);
  // 中断线程
  t.interrupt();
  System.out.println("main over");
}

最终线程会打印出thread over.true。这说明 线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException 

 

第三个维度:一个有序的队列

在AQS中采用CHL列表来解决有序的队列的问题。

AQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。

对于入队列(enqueue):采用CAS操作,每次比较尾结点是否一致,然后插入的到尾结点中。

do {

        pred = tail;

}while ( !compareAndSet(pred,tail,node) );

对于出队列(dequeue):由于每一个节点也缓存了一个状态,决定是否出队列,因此当不满足条件时就需要自旋等待,一旦满足条件就将头结点设置为下一个节点。

while (pred.status != RELEASED) ;

head  = node;

实际上这里自旋等待也是使用LockSupport.park()来实现的。

AQS里面有三个核心字段:

private volatile int state;

private transient volatile Node head;

private transient volatile Node tail;

其中state描述的有多少个线程取得了锁,对于互斥锁来说state<=1。head/tail加上CAS操作就构成了一个CHL的FIFO队列。下面是Node节点的属性。

volatile int waitStatus; 节点的等待状态,一个节点可能位于以下几种状态:

·        CANCELLED = 1: 节点操作因为超时或者对应的线程被interrupt。节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出。

·        SIGNAL = -1: 节点的继任节点是(或者将要成为)BLOCKED状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的继任节点。

·        CONDITION = -2:表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。

·        0: 正常状态,新生的非CONDITION节点都是此状态。

·        非负值标识节点不需要被通知(唤醒)。

volatile Node prev;此节点的前一个节点。节点的waitStatus依赖于前一个节点的状态。

volatile Node next;此节点的后一个节点。后一个节点是否被唤醒(uppark())依赖于当前节点是否被释放。

volatile Thread thread;节点绑定的线程。

Node nextWaiter;下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。

CLH算法实现

CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁。结点之间是通过隐形的链表相连,之所以叫隐形的链表是因为这些结点之间没有明显的next指针,而是通过myPred所指向的结点的变化情况来影响myNode的行为。CLHLock上还有一个尾指针,始终指向队列的最后一个结点。CLHLock的类图如下所示:

 

当一个线程需要获取锁时,会创建一个新的QNode,将其中的locked设置为true表示需要获取锁,然后线程对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前趋的引用myPred,然后该线程就在前趋结点的locked字段上旋转,直到前趋结点释放锁。当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前趋结点。如下图所示,线程A需要获取锁,其myNode域为true,些时tail指向线程A的结点,然后线程B也加入到线程A后面,tail指向线程B的结点。然后线程AB都在它的myPred域上旋转,一量它的myPred结点的locked字段变为false,它就可以获取锁扫行。明显线程AmyPred locked域为false,此时线程A获取到了锁。

整个CLH的代码如下,其中用到了ThreadLocal类,将QNode绑定到每一个线程上,同时用到了AtomicReference,对尾指针的修改正是调用它的getAndSet()操作来实现的,它能够保证以原子方式更新对象引用。

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;

 

public class CLHLock {

   public static class CLHNode {

       private boolean isLocked = true; //默认是在等待锁

    }

 

   @SuppressWarnings("unused" )

   private volatile CLHNode tail ;

    privatestatic final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER =AtomicReferenceFieldUpdater

                  . newUpdater(CLHLock.class,CLHNode .class , "tail" );

 

   public void lock(CLHNode currentThreadCLHNode) {

       CLHNode preNode = UPDATER.getAndSet( this,currentThreadCLHNode); //转载人注释: this里的"tail"值设置成currentThreadCLHNode

       if(preNode != null) {//已有线程占用了锁,进入自旋

           while(preNode.isLocked ) {

           }

       }

    }

 

   public void unlock(CLHNode currentThreadCLHNode) {

       // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。

       if (!UPDATER .compareAndSet(this,currentThreadCLHNode, null)) {

           // 还有后续线程

           currentThreadCLHNode. isLocked = false ;//改变状态,让后续线程结束自旋

       }

    }

}

针对CLS锁,后期优化出现了MCS锁。MCSSpinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。

CLH MCS的比较

下图是CLH锁和MCS锁队列图示:

差异:

  1. 从代码实现来看,CLH比MCS要简单得多。
  2. 从自旋的条件来看,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋
  3. 从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
  4. CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。

注意:这里实现的锁都是独占的,且不能重入的。

 

 

下面从Semaphore CountDownLatch源码实现来说明AQS

①   Semaphore

semaphore代码执行的流程,分析acquire的过程
信号量在多线程中有着重要的应用,它的原理是将资源抽象成信号量,如果信号量大于0表明有可用资源,小于0,则需要等待。
对应申请时,就是做信号量减操作;
对应释放时,就是做信号量加操作。
资源又抽象成state,这是个所有线程都可见的变量。通过state就可以判断线程是不是可以访问共享资源了。


1.new Semaphore
()对象时,它调用的 sync = new NonfairSync(permits);可以看出它是调用非公平的同步辅助类,其中sync的类型是Sync,它的定义如下:
   abstract static class Sync extendsAbstractQueuedSynchronizer
{};
   

2.NonfairSync类又继承Sync类,它具体实现如下:
  final static class NonfairSync extends Sync{

        private static finallong serialVersionUID = -2694183684443567898L;


        NonfairSync(intpermits) {
           super(permits);
/*此处调用的是Sync(intpermits) {
                setState(permits);

             }
最终state的定义是这样的private volatile int state;
这个state对所有的线程是可见的,并且是最新的值。
*/

        }
        //这个是在第三步中的acquireSharedInterruptibly()方法被调用
        protected inttryAcquireShared(int acquires) {

            returnnonfairTryAcquireShared(acquires);
        }
    }
   
3.看看acquire()方法的执行过程
 
先是调用如下的方法:
  public void acquire() throwsInterruptedException {

       sync.acquireSharedInterruptibly(1);
  }
  acquireSharedInterruptibly(1) 这个方法调用下面这个方法:
  public final voidacquireSharedInterruptibly(int arg) throws InterruptedException {

        if(Thread.interrupted())
            thrownew InterruptedException();
//如果小于0,表明没有资源了,就要进入队列中去
        if (tryAcquireShared(arg)< 0)

           doAcquireSharedInterruptibly(arg);
    }

4.tryAcquireShared()方法调用nonfairTryAcquireShared()方法,看看它的具体是怎样实现。
  final int nonfairTryAcquireShared(intacquires) {

            for (;;){
//最新的state值
               int available = getState();

//还剩下多少个许可证了(permit)
               int remaining = available - acquires;

//只有在还剩有时才更新状态
               if (remaining < 0 ||

                   compareAndSetState(available, remaining))
                   return remaining;
            }
        }
 
 5.如果没有许可证了,就要进入队列中去了,看看doAcquireSharedInterruptibly()这个方法的实现
    private voiddoAcquireSharedInterruptibly(int arg)

        throwsInterruptedException {
        final Node node =addWaiter(Node.SHARED);
/*
             private Node addWaiter(Node mode) {
                Node node = new Node(Thread.currentThread(), mode);
                Node pred = tail;
                if (pred != null) {
                    node.prev = pred;
                    if (compareAndSetTail(pred, node)) {
                        pred.next = node;
                        return node;
                    }
                }
//进入队列
                enq(node);

                return node;
             }
*/
        try {
            for (;;){
               final Node p = node.predecessor();
               if (p == head) {
                   int r = tryAcquireShared(arg);
                   if (r >= 0) {
                       setHeadAndPropagate(node, r);
                       p.next = null; // help GC
                       return;
                   }
               }
               if (shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   break;
            }
        } catch (RuntimeExceptionex) {
           cancelAcquire(node);
            throw ex;
        }
        // Arrive here only ifinterrupted
        cancelAcquire(node);
        throw newInterruptedException();
    }

  

②    CountDownLatch

CountDownLatch源代码分析
1.
首先分析它的功能:Asynchronization aid that allows one or more threads to wait until a set ofoperations being performed in other threads completes.
也就是说主线程在等待所有其它的子线程完成后再往下执行。常见的例子是待所有的运动员跑完后再排名。


2.
它所提供的方法如下:
构造函数:CountDownLatch(intcount)//初始化count数目的同步计数器,只有当同步计数器为0,主线程才会向下执行
主要方法:void await()//当前线程等待计数器为0 
      boolean await(long timeout,TimeUnit unit)//
与上面的方法不同,它加了一个时间限制。
      void countDown()//
计数器减1
      long getCount()//
获取计数器的值


3.
它的内部有一个辅助的内部类:sync.
它的实现如下:privatestatic final class Sync extends AbstractQueuedSynchronizer {
        private static finallong serialVersionUID = 4982264981922014374L;

        /*此处在new CountDownLatch时会调用: 
 public CountDownLatch(int count) {

             if (count < 0) throw new IllegalArgumentException("count < 0");
             this.sync = new Sync(count);
        }*/
        Sync(int count) {
           setState(count);
        }


        int getCount() {
            returngetState();
        }


        protected inttryAcquireShared(int acquires) {
            return(getState() == 0) ? 1 : -1;
        }


        protected booleantryReleaseShared(int releases) {
            //Decrement count; signal when transition to zero
            for (;;){
               int c = getState();
               if (c == 0)
                   return false;
               int nextc = c-1;
               if (compareAndSetState(c, nextc))
                   return nextc == 0;
            }
        }
    }

4.await()方法的实现
  sync.acquireSharedInterruptibly(1);

     -->if(tryAcquireShared(arg) < 0)//调用3中的tryAcquireShared()方法
           doAcquireSharedInterruptibly(arg);//
加入到等待队列中

5.countDown
()方法的实现
  sync.releaseShared(1);

     --> if(tryReleaseShared(arg))//调用3中的tryReleaseShared()方法
              doReleaseShared();//
解锁

 

0 0
原创粉丝点击