AQS2:可重入和阻塞
2018-01-11 16:55 faunjoe88 阅读(398) 评论(0) 编辑 收藏 举报本文仅基于可重入的锁(ReentrantLock类)对AQS做分析,只考虑独占锁。
共享锁与独占锁的更多信息,以后再讨论。
AQS中队列的实现
节点Node
AQS的节点包含了对前置节点的引用pre,后置节点的引用next,以及持有节点的线程thread.
static final class Node { /** pre节点,主要用来实现取消 * Link to predecessor node that current node/thread relies on * for checking waitStatus. Assigned during enqueuing, and nulled * out (for sake of GC) only upon dequeuing. Also, upon * cancellation of a predecessor, we short-circuit while * finding a non-cancelled one, which will always exist * because the head node is never cancelled: A node becomes * head only as a result of successful acquire. A * cancelled thread never succeeds in acquiring, and a thread only * cancels itself, not any other node. */ volatile Node prev; /** next节点,主要用来实现阻塞/唤醒 * Link to the successor node that the current node/thread * unparks upon release. Assigned during enqueuing, adjusted * when bypassing cancelled predecessors, and nulled out (for * sake of GC) when dequeued. The enq operation does not * assign next field of a predecessor until after attachment, * so seeing a null next field does not necessarily mean that * node is at end of queue. However, if a next field appears * to be null, we can scan prev's from the tail to * double-check. The next field of cancelled nodes is set to * point to the node itself instead of null, to make life * easier for isOnSyncQueue. */ volatile Node next; //Node 节点 关联的线程 //1.重入时判断是否持有锁的线程 //2.取消阻塞时,unpark节点对应的线程 /** * The thread that enqueued this node. Initialized on * construction and nulled out after use. */ volatile Thread thread; }
AQS的属性
CLH队列中只有tail节点,通过tail节点就可以实现首尾相连的队列。
AQS中则使用head和tail节点实现队列。
入队时,通过tail节点构建CLH队列
出队时,只需要设置head节点(把当前成功获取锁的节点设置为head,则head节点为持有锁的节点)
入队:
/** * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node */ private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); //尝试直接设置tail入队,失败了走完整的enq入队逻辑 // Try the fast path of enq; backup to full enq on failure Node pred = tail; //注意:如果没有调用过enq tail是为空的,下面的判断不成立 //也就是说,enq方法调用之后,尝试直接设置tail入队,才有机会 if (pred != null) { node.prev = pred; //如果入队成功,直接返回 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //完整的入队逻辑 enq(node); return node; } /** 自旋入队(包含了初始化流程) * Inserts node into queue, initializing if necessary. See picture above. * @param node the node to insert * @return node's predecessor */ 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; } } } }
考虑enq不断自旋的情况:
线程1
1.第1次循环
tail为空,初始化
tail=head = new Node(无参构造,空的Node)
+------+
head(tail) | |
+------+
没有return,继续循环
2.第2次循环
如果成功入队
+------+ prev +-----+ head | | <---- | | tail(当前节点) +------+ +-----+
返回前置节点,结束自旋
线程2
3.第1次循环
+------+ prev +-----+ +-----+ head | | <---- | | <---- | | tail +------+ +-----+ +-----+
可重入
AQS为了支持可重入,使用了计数方式,对应属性是 volatile int state.
//AQS的同步状态 /** * The synchronization state. */ private volatile int state;
①某个线
程获取锁的标志就是原子性的把state从某个期望状态(expect)改为指定状态(target)。
如果修改的时候state!=expect,说明state被其他线程改动了,其语义就是:锁被其他线程持有。
当前线程只能自旋/阻塞,直到持有锁的线程把状态改回expect,当前线程才能结束自旋,并且持有锁。
②支持可重入,则需要计数机制,每次重入,state+1,释放则state-1.
阻塞/唤醒
AQS使用LockSupport来阻塞/唤醒线程。
LockSupport针对每个线程操作,发给每个线程一个许可(permit),与blocker无关。
//如果得到许可,则立即返回,否则阻塞当前线程 public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); } //给指定传入的线程许可,解除阻塞 public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
LockSupport不能被滥用。对当前线程调用unpark,可能导致AQS产生不可预知的情况。
park的解除条件
- 其他线程对当前线程调用了unpark
- 其他线程中断了当前线程
- 不可预知的返回
只考虑可重入和阻塞的AQS的简单实现
import com.google.common.collect.Lists; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.LockSupport; /** * Created by 张三丰 on 2017\8\2 0002. */ public class ReentanceAndBlock { static class CLHLock { static class Node { /** * 节点的锁定状态,使用阻塞机制后,不需要这个状态了,获取不到锁的线程被阻塞,并在合适的时机被唤醒 */ // volatile boolean isLocked = true; //下一个节点 volatile Node next; //持有节点的线程 volatile Thread thread; public Node() { } public Node(Thread thread) { this.thread = thread; } } //可重入的状态,aqs使用了int 类型,然后使用了unsafe做cas操作。 // 这里不涉及unsafe,所以使用AtomicInteger private volatile AtomicInteger state = new AtomicInteger(); private volatile Node tailNode; private volatile Node headNode; //这个值只需要持有锁的线程自己可见即可(锁重入时判断,只能是同一个线程) // ,不需要volatile (其他线程见不见不影响) private Thread holdLockThread; private static final AtomicReferenceFieldUpdater<CLHLock, Node> TAIL_UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "tailNode"); private static final AtomicReferenceFieldUpdater<CLHLock, Node> HEAD_UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "headNode"); public void lock(int acquire) { //1.尝试获取锁 if (tryLock(acquire)) { return; } //2.入队 Node current = null; for (; ; ) { Node t = tailNode; if (t == null) { Node node = new Node(); if (TAIL_UPDATER.compareAndSet(this, null, node)) { //初始化时,head节点是一个假的节点,绑定的是当前持有锁的线程 //以后也会一直保持绑定持有锁的节点 headNode = tailNode; } } else { Node node = new Node(Thread.currentThread()); if (TAIL_UPDATER.compareAndSet(this, t, node)) { t.next = node; current = node; break; } } } //3.自旋获取锁,或阻塞后等待唤醒 for (; ; ) { //头节点是持有锁的节点,检查头节点,竞争锁 if (headNode.next == current) { //前置节点是头节点,不能阻塞,一直自旋尝试获取锁 //这种情况是为了防止,被唤醒后,恰好有另外一个线程获取了锁,竞争失败的情况 if (tryLock(acquire)) { headNode = current; break; } // System.out.println("自旋获取锁~~"); } else { //打印队列数 // System.out.println("====" + __getCount()); //阻塞当前线程,等待前置节点唤醒 LockSupport.park(this); } } } /** * 获取队列数 * * @return */ private int __getCount() { int count = 1; Node node = headNode.next; while (node != null) { count++; node = node.next; } return count; } /** * 定义获取锁的逻辑,AQS中此类由具体子类实现 * @param acquire * @return */ private boolean tryLock(int acquire) { int state = this.state.get(); if (state == 0 && this.state.compareAndSet(0, acquire)) { //获取锁成功,不需要入队 holdLockThread = Thread.currentThread(); // __print1(); return true; } else if (holdLockThread == Thread.currentThread()) { //线程重入 this.state.set(this.state.get() + acquire); return true; } return false; } private void __print1() { if (headNode != null) { Node node = headNode.next; boolean isInCLH = false; while (node != null) { if (node.thread == Thread.currentThread()) { isInCLH = true; break; } node = node.next; } if (!isInCLH) { System.out.println("厉害了,在唤醒的线程之前抢到了锁"); } } } /** * */ public void unlock(int acquire) { // System.out.println("unlock" + Thread.currentThread()); if (tryRelease(acquire)) { holdLockThread = null; state.set(0); unparkNext(); } } /** * 定义释放锁的逻辑,AQS中此类由具体子类实现 * @param acquire * @return */ private boolean tryRelease(int acquire) { //只有持有锁的线程才能释放锁 if (Thread.currentThread() != holdLockThread) { System.out.println("不是hold" + holdLockThread); return false; } int newValue = state.get() - acquire; //不会执行到 if (newValue < 0) { throw new RuntimeException("state < 0"); } //减去计数 state.set(newValue); return newValue == 0; } private void unparkNext() { Node headNode = this.headNode; if (headNode != null) { Node next = headNode.next; if (next != null) { //唤醒下个节点 LockSupport.unpark(next.thread); } } } } static class CLHTester { public static void main(String[] args) throws InterruptedException { testLockAndUnlock(); testReentrance(); } private static void testReentrance() { System.out.println("testReentrance"); final Long[] number = {0L}; ExecutorService executorService = Executors.newFixedThreadPool(10); CLHLock lock = new CLHLock(); for (int i = 0; i < 100; i++) { executorService.execute(() -> { lock.lock(1); Long n = number[0]; if (n == 50) { System.out.println("重入之前计数:" + lock.state); System.out.println("锁重入2"); lock.lock(2); System.out.println("重入之后计数:" + lock.state); lock.unlock(2); System.out.println("锁释放2"); System.out.println("释放之后计数:" + lock.state); } number[0] = n + 1; System.out.println(number[0]); lock.unlock(1); }); } executorService.shutdown(); } private static void testLockAndUnlock() { System.out.println("testLockAndUnlock"); //测试锁,循环100次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 100 List<Integer> sharedValue = Lists.newArrayList(new Integer(0)); ExecutorService executorService = Executors.newFixedThreadPool(50); CLHLock lock = new CLHLock(); for (int i = 0; i < 100; i++) { executorService.execute(() -> { lock.lock(1); //把数据+1 sharedValue.add(0, sharedValue.get(0) + 1); //输出的结构必然是按顺序的 System.out.println(sharedValue.get(0)); // System.out.println(sharedValue.get(0) + "=========" + Thread.currentThread().toString()); lock.unlock(1); }); } executorService.shutdown(); while (!executorService.isTerminated()) { } } } }