AbstractQueuedSynchronizer是JUC的核心框架,其设计很是精妙。 使用了Java的模板方法模式。 首先试图还原一下其使用场景:
对于排他锁,在同一时刻,N个线程只有1个线程能获取到锁;其余没有获取到锁的线程被挂起放置在队列中,待获取锁的线程释放锁后,再唤醒队列中的线程。java
线程的挂起是获取锁失败时调用Unsafe.park()方法;线程的唤醒是由其余线程释放锁时调用Unsafe.unpark()实现。
因为获取锁,执行锁内代码逻辑,释放锁整个流程可能只须要耗费几毫秒,因此很难对锁的争用有一个直观的感觉。下面以3个线程来简单模拟一下排他锁的机制。node
import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.LockSupport; public class AQSDemo { private static final Unsafe unsafe = getUnsafe(); private static final long stateOffset; private static Unsafe getUnsafe() { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe)field.get(null); } catch (Exception e) { } return null; } static{ try{ stateOffset = unsafe.objectFieldOffset (AQSDemo.class.getDeclaredField("state")); } catch (Exception ex) { throw new Error(ex); } } private volatile int state; private List<Thread> threads = new ArrayList<>(); public void lock(){ if(!unsafe.compareAndSwapInt(state,stateOffset,0,1)){ // 有问题,非线程安全;只做演示使用 threads.add(Thread.currentThread()); LockSupport.park(); Thread.interrupted(); } } public void unlock(){ state = 0; if(!threads.isEmpty()){ Thread first = threads.remove(0); LockSupport.unpark(first); } } static class MyThread extends Thread{ private AQSDemo lock; public MyThread(AQSDemo lock){ this.lock = lock; } public void run(){ try{ lock.lock(); System.out.println("run "); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public static void main(String[] args) { AQSDemo lock = new AQSDemo(); MyThread a1 = new MyThread(lock); MyThread a2 = new MyThread(lock); MyThread a3 = new MyThread(lock); a1.start(); a2.start(); a3.start(); } }
上面的代码,使用park和unpark简单模拟了排他锁的工做原理。使用ArrayList屏蔽了链表多线程环境下链表的构造细节, 该代码实际上在多线程环境中使用是有问题的,发现了么?编程
经过上面的代码,能理解到多线程环境下,链表为何能比ArrayList好使。安全
理解AQS, 其核心在于理解state
和head
, tail
三个变量。换句话说,理解AQS, 只需理解状态
和链表实现的队列
这两样东西。其使用方式就是,若是更新状态不成功,就把线程挂起,丢到队列中;其余线程使用完毕后,从队列中唤醒一个线程执行。 若是排队的线程数量过多,那么该谁首先得到锁就有讲究,不能暗箱操做,因此有公平和非公平两种策略。数据结构
愈来愈能理解 “编程功底,细节是魔鬼”,理解了上面的使用方式,只至关于理解了需求。那么实现上有那些细节呢? 咱们经过问答的方式来阐明。多线程
问题1: state
变量为何要用volatile关键词修饰?框架
volatile是synchronized的轻量版本,在特定的场景下具有锁的特色变量更新的值不依赖于当前值
, 好比setState()
方法。 当volatile的场景不知足时,使用Unsafe.compareAndSwap便可。ide
问题2: 链表是如何保证多线程环境下的链式结构?学习
首先咱们看链表是一个双向链表,咱们看链表呈现的几个状态:ui
1. 空链表 (未初始化) head -- null tail -- null or (初始化后) head -- Empty Node tail -- Empty Node 2. 只有一个元素的链表 head -- Empty Node <-> Thread Node -- tail
也就是说,当链表的不为空时, 链表中填充者一个占位节点。
学习数据结构,把插入删除两个操做弄明白,基本就明白这个数据结构了。咱们先看插入操做enq()
:
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; } } } }
首先一个无限循环。 假如这个链表没有初始化,那么这个链表会经过循环的结构插入2个节点。 因为多线程环境下, compareAndSet会存在失败,因此经过循环保证了失败重试。 为了保证同步,要么依赖锁,要么经过CPU的cas。 这里是实现同步器,只能依赖cas。 这种编程结构,看AtomicInteger,会特别熟悉。
接下来看链表的删除操做。当线程释放锁调用release()
方法时,AQS会按线程进入队列的顺序唤醒地一个符合条件的线程,这就是FIFO的体现。代码以下:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
这里unparkSuccessor()
里面的waitStatus
咱们先忽略。这样的话,线程会从阻塞的后面继续执行,从parkAndCheckInterrupt()
方法中出来。
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)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
因为唤醒的顺序是FIFO, 因此一般p==head
条件是知足的。若是获取到锁,就把当前节点做为链表的head节点:setHead(node)
, 原head节点从链表中断开,让GC回收p.next=null
。 也就是说,链表的删除是从头开始删除,以实现FIFO的目标。
到这里,AQS的链表操做就弄清楚了。接下来的疑问就在节点的waitStatus
里面。
问题: waitStatus的做用是什么?
在AQS, 实现了一个ConditionObject, 就像Object.wait/nofity必须在synchronized中调用同样, JUC实现了一个Object.wait/notify的替代品。这是另外一个话题,这里不细说了,后面再研究一下。
最后,总结一下,本文简单分析了一下AQS的实现机制。主要参考ReentrantLock和论文《The java.util.concurrent Synchronizer Framework》。