java中锁的深入理解(二)
来源:互联网 发布:贵阳大数据广场在哪里 编辑:程序博客网 时间:2024/05/18 01:55
上一篇讲到了synchronized实现原理,这一篇一起来学习一下Lock的底层实现原理。
java.util.concurrent包中有很多lock接口的实现类,ReentrantLock,ReadWriteLock,这在我的另一篇文章中也提到了。
这些实现类的内部都使用了队列同步器AbstractQueuedSynchronizer类作为依赖。下面我们用AQS来指代AbstractQueuedSynchronizer。
AQS定义:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //等待队列的头节点 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;} ...}AQS中使用int型变量state来表示同步状态(0表示空闲,1表示被占中),通过内置的队列实现线程的排队工作。同步状态state,队列头指针head,队列尾指针tail都使用了volatile进行修饰,保证了多线程之间的可见性。
内部队列的定义:
static final class Node { static final Node SHARED = new Node(); static final 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 Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; ... }
队列中的每个节点存储了前缀节点prev,后缀节点next,节点中的线程thread,节点的状态waitStatus,状态的定义有四种,CANCELLED取消状态,SIGNAL等待触发状态,CONDITION等待条件状态,PROPAGATE状态需要向后传播。整个队列中,只有头节点中的线程是当前执行线程。
在并发的情况中,如果state的值为0,即状态为空闲,那么多个线程同时执行CAS修改state的值,成功修改state值的线程获得该锁。未获得锁的线程将生成新的节点,使用CAS的方式向后排队。当线程排到队列后面时,并不会马上插入,而是进行一个自旋操作。
final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } catch (Throwable t) { cancelAcquire(node); throw t; } }
因为在node的排队的过程,头节点中的线程(即之前在执行的线程)可能已经执行完成,所以要判断该node的前一个节点pred是否为head节点(代表正在执行线程),如果pred == head,表明当前节点是队列中第一个“有效的”节点,因此再次尝试tryAcquire获取锁,如果获取到了锁,就直接将该线程设置到头节点,否则,再进行判断:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ pred.compareAndSetWaitStatus(ws, Node.SIGNAL); } return false; }如果前缀节点的状态为SIGNAL,表示这个节点已经设置了锁一释放就通知它的状态,那么我们就可以安全的将线程插在它的后面,并返回true。如果前缀节点的状态是取消状态,那么就跳过前缀节点,找到前面没有被取消的第一个节点,插在其后面。否则,前缀节点的状态要么是0,要么是状态向后发散,我们直接将当前节点的状态设置为SIGNAL。以上两种情况,都无须向队列中插入,所以均返回false。
线程每次被唤醒时,都要进行中断检测,如果发现当前线程被中断,那么抛出InterruptedException并退出循环。从无限循环的代码可以看出,并不是被唤醒的线程一定能获得锁,必须调用tryAccquire重新竞争,因为锁是非公平的,有可能被新加入的线程获得,从而导致刚被唤醒的线程再次被阻塞,这个细节充分体现了“非公平”的精髓。
private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) node.compareAndSetWaitStatus(ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node p = tail; p != node && p != null; p = p.prev) if (p.waitStatus <= 0) s = p; } if (s != null) LockSupport.unpark(s.thread); }
线程释放锁的过程:
如果节点的状态小于0.将节点的状态设为0。然后找到下面状态值小于0的线程,将其唤醒。
总结:
Doug Lea大神写的代码相当牛,只看源码,不看应用,很难完全理解透彻,但源码看下来总体思路还是很清晰的。
- java中锁的深入理解(二)
- java中锁的深入理解(一)
- java中clone的深入理解
- 深入理解java中HashMap的使用
- java中clone的深入理解
- 深入理解 Java中 的 final 关键字
- 深入理解java语言的class文件格式(二)
- [深入理解JVM 二]---Java的编译过程
- 深入理解java中数组
- Java中static深入理解
- java中编码深入理解
- 深入理解Java虚拟机读书笔记(二)
- 深入理解Java Class文件格式(二)
- 深入理解Java虚拟机读书笔记二
- 深入理解Java Class文件格式(二)
- 深入理解Java Class文件格式(二)
- 深入理解Java Class文件格式(二)
- 深入理解Java Class文件格式(二)
- Unreal Engin_画廊制作笔记 _011<利用PS为场景添加滤镜效果>
- Mysql 优化
- opencv(10)---对比度亮度调整与通道分离与合并
- WHERE子句使用正则表达式进行搜索过滤
- 第八单元
- java中锁的深入理解(二)
- 数据结构——基础篇
- python数据分析:pandas数据结构与操作
- 浅谈嵌入式驱动设计
- lintcode(661)Convert BST to Greater Tree
- Python学习之 logging模块的使用
- 《程序员修炼之道》笔记(六)
- 第五次实验--数组—
- 学习AIDL遇到的问题