JAVA并发-AQS知识笔记
概述
AQS是AbstractQueuedSynchronizer的缩写,翻译成中文就是抽象队列同步器,AbstractQueuedSynchronizer这个类也是在java.util.concurrent.locks下面。简单来说AQS定义了一套多线程访问共享资源的同步器框架,这套框架定义了共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,AQS也是一个依赖状态state的同步器,而且java并发编程的核心包java.concurrent.util都需要这套框架。比如Lock,Latch,Barrier等,都是基于AQS框架实现。
我们在学习一套并发工具的时候,我们首先要抓住这3点:
状态:一般是一个state属性,它基本是整个工具的核心,通常整个工具都是在设置和修改状态,很多方法的操作都依赖于当前状态是什么。由于状态是全局共享的,一般会被设置成volatile类型,以保证其修改的可见性。
队列:队列通常是一个等待对象 Node 的集合,大多数以链表的形式实现。队列采用的是悲观锁的思想,表示当前所等待的资源,状态或者条件短时间内可能无法满足。因此,它会将当前线程包装成某种 类型的数据结构 Node ,放入一个等待队列中,当一定条件满足后,再从等待队列中取出。
CAS:CAS操作是最轻量的并发处理,通常我们对于状态的修改都会用到CAS操作,因为状态可能被多个线程同时修改,CAS操作保证了同一个时刻,只有一个线程 能修改成功,从而保证了线程安全,CAS操作基本是由Unsafe工具类的compareAndSwapXXX来实现的;CAS采用的是乐观锁的思想,因 此常常伴随着自旋,如果发现当前无法成功地执行CAS,则不断重试,直到成功为止,自旋的的表现形式通常是一个死循环for(;;)。
AQS具备特性
特点:1,阻塞等待队列;2,共享/独占;3,公平/非公平;4,可重入;5,允许中断。
first-in-first-out (FIFO) wait queues
blocking locks and related synchronizers (semaphores, events, etc)
乐观锁
共享锁shared是一个乐观锁。可以允许多个线程阻塞点,可以多个线程同时获取到锁。它允许一个资源可以被多个读操作,或者被一个写操作访问,但是两个操作不能同时访问。
Java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值版本号是否一样,一样的更新,否则失败。
悲观锁
独占锁exclusive是一个悲观锁。保证只有一个线程经过一个阻塞点,只有一个线程可以获得锁。
Java中的悲观锁就是synchronized,AQS框架下的锁则是先尝试CAS乐观锁去获取,获取不到才会转为悲观锁,如ReentrantLock
大量使用了CAS操作, 并且在冲突时,采用自旋方式重试,以实现轻量级和高效地获取锁。
AQS可以实现独占锁和共享锁
通 过一个CLH队列实现的(CLH锁即Craig, Landin, and Hagersten (CLH) locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本 地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。)
AQS框架
AbstractQueuedSynchronizer是JDK实现其他同步工具的基础。
AQS内部封装了一个状态volatile int state用来表示资源,提供了独占以及共享两种操作:acquire(acquireShare)/release(releaseShare)。
acquire的语义是:获取资源,如果当前资源满足条件,则直接返回,否则挂起当前线程
release的语义是:释放资源,唤醒挂起线程
它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:getState()、setState()、compareAndSetState()
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将 state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会 获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能 保证state是能回到零态的。
再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个 子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然 后主调用线程就会从await()函数返回,继续后余动作。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、 tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如 ReentrantReadWriteLock。
双向CLH链表
AQS核心业务逻辑
1.AQS中用state属性表示锁同步状态,如果能成功将state属性通过CAS操作从0设置成1即获取了锁. 当state>0时表示已经获取了锁,当state = 0无锁。
2.获取了锁的线程才能将exclusiveOwnerThread设置成自己
3.addWaiter负责将当前等待锁的线程包装成Node,并成功地添加到队列的末尾,这一点是由它调用的enq方法保证的,enq方法同时还负责在队列为空时初始化队列。
4.acquireQueued方法用于在Node成功入队后,继续尝试获取锁(取决于Node的前驱节点是不是head),或者将线程挂起
5.shouldParkAfterFailedAcquire方法用于保证当前线程的前驱节点的waitStatus属性值为SIGNAL,从而保证了自己挂起后,前驱节点会负责在合适的时候唤醒自己。
6.parkAndCheckInterrupt方法用于挂起当前线程,并检查中断状态。
7.如果最终成功获取了锁,线程会从lock()方法返回,继续往下执行;否则,线程会阻塞等待。
AQS三板斧
状态
volatile state属性
private volatile int state;
该属性的值即表示了锁的状态,state为0表示锁没有被占用,state大于0表示当前已经有线程持有该锁,这里之所以说大于0而不说等于1是因为可能存在可重入的情况。你可以把state变量当做是当前持有该锁的线程数量。
CAS 操作用来改变状态
waitStatus 的状态值
它不是表征当前节点的状态,而是当前节点的下一个节点的状态。
当 一个节点的waitStatus被置为SIGNAL,就说明它的下一个节点(即它的后继节点)已经被挂起了(或者马上就要被挂起了),因此在当前节点释放 了锁或者放弃获取锁时,如果它的waitStatus属性为SIGNAL,它还要完成一个额外的操作——唤醒它的后继节点。
表示Node所代表的当前线程已经取消了排队,即放弃获取锁了。
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
CAS操作
AQS的3个属性state,head和tail
Node对象的两个属性waitStatus,next
CAS操作主要针对5个属性。
1 private static final Unsafe unsafe = Unsafe.getUnsafe();
2 private static final long stateOffset;
3 private static final long headOffset;
4 private static final long tailOffset;
5 private static final long waitStatusOffset;
6 private static final long nextOffset;
7
8 static {
9 try {
10 stateOffset = unsafe.objectFieldOffset
11 (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
12 headOffset = unsafe.objectFieldOffset
13 (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
14 tailOffset = unsafe.objectFieldOffset
15 (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
16 waitStatusOffset = unsafe.objectFieldOffset
17 (Node.class.getDeclaredField("waitStatus"));
18 nextOffset = unsafe.objectFieldOffset
19 (Node.class.getDeclaredField("next"));
20
21 } catch (Exception ex) { throw new Error(ex); }
22 }
CAS操作码
CAS操作是最轻量的并发处理,通常我们对于状态的修改都会用到CAS操作,因为状态可能被多个线程同时修改,CAS操作保证了同一个时刻,只有一个线程能修改成功,从而保证了线程安全,CAS操作基本是由Unsafe工具类的compareAndSwapXXX
来实现的;CAS采用的是乐观锁的思想,因此常常伴随着自旋,如果发现当前无法成功地执行CAS,则不断重试,直到成功为止,自旋的的表现形式通常是一个死循环for(;;);
1 protected final boolean compareAndSetState(int expect, int update) {
2 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
3 }
4 private final boolean compareAndSetHead(Node update) {
5 return unsafe.compareAndSwapObject(this, headOffset, null, update);
6 }
7 private final boolean compareAndSetTail(Node expect, Node update) {
8 return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
9 }
10 private static final boolean compareAndSetWaitStatus(Node node, int expect,int update) {
11 return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
12 }
13 private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
14 return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
15 }
队列
AQS中,队列的实现是一个双向链表,被称为sync queue,它表示所有等待锁的线程的集合
AQS 中的队列是一个CLH队列,它的head节点永远是一个哑结点(dummy node), 它不代表任何线程(某些情况下可以看做是代表了当前持有锁的线程),因此head所指向的Node的thread属性永远是null。只有从次头节点往后 的所有节点才代表了所有等待锁的线程。也就是说,在当前线程没有抢到锁被包装成Node扔到队列中时,即使队列是空的,它也会排在第二个,我们会在它的前 面新建一个dummy节点
在并发编程中使用队列通常是将当前线程包装成某种类型的数据结构扔到等待队列中.
队列中的节点数据结构。
1 static final class Node {
2
3 // 共享
4 static final Node SHARED = new Node();
5 // 独占
6 static final Node EXCLUSIVE = null;
7
8 /**
9 * 因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态
10 */
11 static final int CANCELLED = 1;
12 /**
13 * 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
14 * (说白了就是处于等待被唤醒的线程(或是节点)只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行)
15 */
16 static final int SIGNAL = -1;
17 /**
18 * 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()后,该节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
19 */
20 static final int CONDITION = -2;
21 /**
22 * 表示下一次共享式同步状态获取,将会无条件地传播下去
23 */
24 static final int PROPAGATE = -3;
25
26 /** 等待状态 */
27 volatile int waitStatus;
28
29 /** 前驱节点,当节点添加到同步队列时被设置(尾部添加) */
30 volatile Node prev;
31
32 /** 后继节点 */
33 volatile Node next;
34
35 /** 等待队列中的后续节点。如果当前节点是共享的,那么字段将是一个 SHARED 常量,也就是说节点类型(独占和共享)和等待队列中的后续节点共用同一个字段 */
36 Node nextWaiter;
37
38 /** 获取同步状态的线程 */
39 volatile Thread thread;
40
41 final boolean isShared() {
42 return nextWaiter == SHARED;
43 }
44
45 final Node predecessor() throws NullPointerException {
46 Node p = prev;
47 if (p == null)
48 throw new NullPointerException();
49 else
50 return p;
51 }
52
53 Node() { // Used to establish initial head or SHARED marker
54 }
55
56 Node(Thread thread, Node mode) { // Used by addWaiter
57 this.nextWaiter = mode;
58 this.thread = thread;
59 }
60
61 Node(Thread thread, int waitStatus) { // Used by Condition
62 this.waitStatus = waitStatus;
63 this.thread = thread;
64 }
65
66 }
状态变量waitStatus
表示当前Node所代表的线程的等待锁的状态,在独占锁模式下,我们只需要关注CANCELLED SIGNAL两种状态即可。
nextWaiter属性
在独占锁模式下永远为null,仅仅起到一个标记作用,没有实际意义。
AQS2种队列
同步等待队列
AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。
不过这里有一点我们提前说一下,在AQS中的队列是一个CLH队列,它的head节点永远是一个哑结点(dummy node), 它不代表任何线程(某些情况下可以看做是代表了当前持有锁的线程),因此head所指向的Node的thread属性永远是null。只有从次头节点往后的所有节点才代表了所有等待锁的线程。也就是说,在当前线程没有抢到锁被包装成Node扔到队列中时,即使队列是空的,它也会排在第二个,我们会在它的前面新建一个dummy节点(具体的代码我们在后面分析源码时再详细讲)。为了便于描述,下文中我们把除去head节点的队列称作是等待队列,在这个队列中的节点才代表了所有等待锁的线程。
thread
:表示当前Node所代表的线程
waitStatus
:表示节点所处的等待状态,共享锁模式下只需关注三种状态:SIGNAL
CANCELLED
初始态(0)
prev
next
:节点的前驱和后继
nextWaiter
:进作为标记,值永远为null,表示当前处于独占锁模式
条件等待队列
Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁。
AQS核心属性
锁相关的属性有两个
private volatile int state; //锁的状态
private transient Thread exclusiveOwnerThread; // 当前持有锁的线程,注意这个属性是从AbstractOwnableSynchronizer继承而来
sync queue相关的属性有两个
private transient volatile Node head; // 队头,为dummy node
private transient volatile Node tail; // 队尾,新入队的节点
队列中的Node属性
1 // 节点所代表的线程
2 volatile Thread thread;
3
4 // 双向链表,每个节点需要保存自己的前驱节点和后继节点的引用
5 volatile Node prev;
6 volatile Node next;
7
8 // 线程所处的等待锁的状态,初始化时,该值为0
9 volatile int waitStatus;
10 static final int CANCELLED = 1;
11 static final int SIGNAL = -1;
acquire分析
tryAcquire()尝试直接去获取资源,如果成功则直接返回;
addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
如果线程在等待过程中被中断过,先不响应的。在获取资源后才再进行自我中断selfInterrupt()。
tryAcquire(arg) : 获取锁的业务逻辑
判断当前锁有没有被占用:
1.如果锁没有被占用, 尝试以公平的方式获取锁
2.如果锁已经被占用, 检查是不是锁重入
获取锁成功返回true, 失败则返回false
addWaiter(Node mode)
当tryAcquire失败后,才会调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg),addWaiter方法用于将当前线程加入到等待队列的队尾,并返回当前线程所在的结点。
使用了自旋保证插入队尾成功。
在获取锁失败后调用, 将当前请求锁的线程包装成Node扔到sync queue中去,并返回这个Node。
1 private Node addWaiter(Node mode) {
2 Node node = new Node(Thread.currentThread(), mode);
3 // Try the fast path of enq; backup to full enq on failure
4 Node pred = tail;
5 // 如果队列不为空, 则用CAS方式将当前节点设为尾节点
6 if (pred != null) {
7 node.prev = pred;
8 // 检查tail的状态,如果当前是pred
9 if (compareAndSetTail(pred, node)) { // 将当前节点设为尾节点
10 pred.next = node; // 把tail的next节点指向当前Node
11 return node;
12 }
13 }
14
15
16 // 代码会执行到这里, 只有两种情况:
17 // 1. 队列为空
18 // 2. CAS失败
19 // 注意, 这里是并发条件下, 所以什么都有可能发生, 尤其注意CAS失败后也会来到这里. 例如: 有可能其他线程已经成为了新的尾节点,导致尾节点不再是我们之前看到的那个pred了。
20
21 // 如果当前node插入队尾失败,则通过自旋保证替换成功(自旋+CAS)
22 enq(node);
23 return node;
24 }
enq()方法:在该方法中, 我们使用了死循环, 即以自旋方式将节点插入队列,如果失败则不停的尝试, 直到成功为止, 另外, 该方法也负责在队列为空时, 初始化队列,这也说明,队列是延时初始化的(lazily initialized):
1 private Node enq(final Node node) {
2 for (;;) {
3 Node t = tail;
4 // 如果是空队列, 首先进行初始化
5 // 这里也可以看出, 队列不是在构造的时候初始化的, 而是延迟到需要用的时候再初始化, 以提升性能
6 if (t == null) {
7 // 注意,初始化时使用new Node()方法新建了一个dummy节点
8 // 从这里可以看出, 在这个等待队列中,头结点是一个“哑节点”,它不代表任何等待的线程。
9 // head节点不代表任何线程,它就是一个空节点!
10 if (compareAndSetHead(new Node()))
11 tail = head; // 这里仅仅是将尾节点指向dummy节点,并没有返回
12 } else {
13 // 到这里说明队列已经不是空的了, 这个时候再继续尝试将节点加到队尾
14
15 // 1.设置node的前驱节点为当前的尾节点
16 node.prev = t;
17
18 // 2.修改tail属性,使它指向当前节点; 这里的CAS保证了同一时刻只有一个节点能成为尾节点,其他节点将失败,失败后将回到for循环中继续重试。
19 if (compareAndSetTail(t, node)) {
20
21 // 3.修改原来的尾节点,使它的next指向当前节点
22 t.next = node;
23 return t;
24 }
25 }
26 }
27 }
添加到queue队尾步骤
将一个节点node添加到sync queue的末尾需要三步:
设置node的前驱节点为当前的尾节点:node.prev = t
修改tail属性,使它指向当前节点
修改原来的尾节点,使它的next指向当前节点尾分叉。
需要注意,这里的三步并不是一个原子操作,第一步很容易成功;而第二步由于是一个CAS操作,在并发条件下有可能失败,第三步只有在第二步成功的条件下才执行。这里的CAS保证了同一时刻只有一个节点能成为尾节点,其他节点将失败,失败后将回到for循环中继续重试所以,当有大量的线程在同时入队的时候,同一时刻,只有一个线程能完整地完成这三步,而其他线程只能完成第一步,于是就出现了尾分叉:
这 里第三步是在第二步执行成功后才执行的,这就意味着,有可能即使我们已经完成了第二步,将新的节点设置成了尾节点,此时原来旧的尾节点的next值可能还 是null(因为还没有来的及执行第三步),所以如果此时有线程恰巧从头节点开始向后遍历整个链表,则它是遍历不到新加进来的尾节点的,但是这显然是不合 理的,因为现在的tail已经指向了新的尾节点。
另一方面,当我们完成了第二步之后,第一步一定是完成了的,所以如果我们从尾节点开始向前遍历,已经可以遍历到所有的节点。
这也就是为什么我们在AQS相关的源码中 (比如:unparkSuccessor(Node node) 中的:
1 for (Node t = tail; t != null && t != node; t = t.prev))
通常是从尾节点开始逆向遍历链表——因为一个节点要能入队,则它的prev属性一定是有值的,但是它的next属性可能暂时还没有值。
至于那些“分叉”的入队失败的其他节点,在下一轮的循环中,它们的prev属性会重新指向新的尾节点,继续尝试新的CAS操作,最终,所有节点都会通过自旋不断的尝试入队,直到成功为止。
acquireQueued(final Node node, int arg)
addWaiter的将当前线程加入队列后,使用acquireQueued进行阻塞,直到获取到资源后返回
1 final boolean acquireQueued(final Node node, int arg) {
2 boolean failed = true;
3 try {
4 boolean interrupted = false;
5 for (;;) {
6 final Node p = node.predecessor();
7 // 当前节点的前驱是 head 节点时, 再次尝试获取锁
8 if (p == head && tryAcquire(arg)) {
9 setHead(node);
10 p.next = null; // help GC
11 failed = false;
12 return interrupted;
13 }
14 //在获取锁失败后, 判断是否需要把当前线程挂起
15 if (shouldParkAfterFailedAcquire(p, node) &&
16 parkAndCheckInterrupt())
17 interrupted = true;
18 }
19 } finally {
20 if (failed)
21 cancelAcquire(node);
22 }
23 }
shouldParkAfterFailedAcquire(Node pred, Node node)
这个函数只有在当前节点的前驱节点的waitStatus状态本身就是SIGNAL的时候才会返回true, 其他时候都会返回false:
1 // Returns true if thread should block.
2 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
3 int ws = pred.waitStatus; // 获得前驱节点的ws
4 if (ws == Node.SIGNAL)
5 // 前驱节点的状态已经是SIGNAL了(This node has already set status asking a release),说明闹钟已经设了,可以直接高枕无忧地睡了(so it can safely park)
6 return true;
7 if (ws > 0) {
8 // 当前节点的 ws > 0, 则为 Node.CANCELLED 说明前驱节点已经取消了等待锁(由于超时或者中断等原因)
9 // 既然前驱节点不等了, 那就继续往前找, 直到找到一个还在等待锁的节点
10 // 然后我们跨过这些不等待锁的节点, 直接排在等待锁的节点的后面 (是不是很开心!!!)
11 do {
12 node.prev = pred = pred.prev;
13 } while (pred.waitStatus > 0);
14 pred.next = node;
15 } else {
16 // 前驱节点的状态既不是SIGNAL,也不是CANCELLED
17 // 用CAS设置前驱节点的ws为 Node.SIGNAL,给自己定一个闹钟
18 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
19 }
20 return false;
21 }
parkAndCheckInterrupt()
到这个函数已经是最后一步了, 就是将线程挂起, 等待被唤醒. Convenience method to park and then check if interrupted. return true if interrupted:
1 private final boolean parkAndCheckInterrupt() {
2 LockSupport.park(this); // 线程被挂起,停在这里不再往下执行了
3 return Thread.interrupted();
4 }
LockSupport.park()
public class LockSupport extends Object用于创建锁和其他同步类的基本线程阻塞原语:
1 public static void park(Object blocker) {
2 Thread t = Thread.currentThread();
3 setBlocker(t, blocker);
4 UNSAFE.park(false, 0L);
5 setBlocker(t, null);
6 }
7
8 private static void setBlocker(Thread t, Object arg) {
9 // Even though volatile, hotspot doesn't need a write barrier here.
10 UNSAFE.putObject(t, parkBlockerOffset, arg);
11 }
总结
感谢网络大神的分享,
https://juejin.im/post/5aeb07ab6fb9a07ac36350c8
https://www.cnblogs.com/waterystone/p/4920797.html
https://mp.weixin.qq.com/s?__biz=MzA5OTI2MTE3NA==&mid=2658337633&idx=1&sn=6a18fc2310406a2f35ccd4bb7db41a54&chksm=8b02acf8bc7525ee5714d223efd4c27b41ae7d938518f8de8e52faa8a68601167699aac73b9f&mpshare=1&scene=1&srcid=0105vqyUJ4LKmg9TDVyoQdDk&sharer_sharetime=1578208728620&sharer_shareid=d40e8d2bb00008844e69867bcfc0d895#rd
https://www.zfl9.com/java-juc-framework.html
JAVA并发-AQS知识笔记的更多相关文章
- [Java并发] AQS抽象队列同步器源码解析--锁获取过程
要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDo ...
- [Java并发] AQS抽象队列同步器源码解析--独占锁释放过程
[Java并发] AQS抽象队列同步器源码解析--独占锁获取过程 上一篇已经讲解了AQS独占锁的获取过程,接下来就是对AQS独占锁的释放过程进行详细的分析说明,废话不多说,直接进入正文... 锁释放入 ...
- java并发基础知识
这几天全国都是关键时候,放假了,还是要学习啊!很久没有写博客了,最近看了一本书,有关于java并发编程的,书名叫做“java并发编程之美”,讲的很有意思,这里就做一个笔记吧! 有需要openjdk8源 ...
- Java多线程基础知识笔记(持续更新)
多线程基础知识笔记 一.线程 1.基本概念 程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 进程(process):是程序的一次执行过程,或是 ...
- Java并发编程学习笔记 深入理解volatile关键字的作用
引言:以前只是看过介绍volatile的文章,对其的理解也只是停留在理论的层面上,由于最近在项目当中用到了关于并发方面的技术,所以下定决心深入研究一下java并发方面的知识.网上关于volatile的 ...
- Java 并发AQS
转载出处:http://www.cnblogs.com/waterystone/ 一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQu ...
- Java并发编程读书笔记(一)
----------------------------------------------<Java并发编程实战>读书笔记-------------------------------- ...
- Java并发编程学习笔记
Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...
- Java并发编程实战.笔记十一(非阻塞同步机制)
关于非阻塞算法CAS. 比较并交换CAS:CAS包含了3个操作数---需要读写的内存位置V,进行比较的值A和拟写入的新值B.当且仅当V的值等于A时,CAS才会通过原子的方式用新值B来更新V的值,否则不 ...
随机推荐
- [BUUCTF]PWN——roarctf_2019_easy_pwn(详解)
roarctf_2019_easy_pwn 附件 步骤: 例行检查,64位程序,保护全开 试运行一下程序,看看大概的情况,经典的堆块的菜单 64位ida载入,改了一下各个选项的函数名,方便看程序(按N ...
- Offset函数(Excel函数集团)
此处文章均为本妖原创,供下载.学习.探讨! 文章下载源是Office365国内版1Driver,如有链接问题请联系我. 请勿用于商业!谢谢 下载地址:https://officecommunity-m ...
- Python第三周 数据类型:集合set、文件的读写、追加操作。
集合 知识点:集合是无序的 格式:{1,2,3,"str_test"} set_1 = set(list1)#将列表转换为集合 集合关系测试: 集合的逻辑判断.取交集.并集.差集. ...
- 如何在Uni-app中通过腾讯IM SDK实现社交应用和直播互动等功能
Uni-app想开发社交应用.IM.店铺客服.嵌入式社交模块.在线直播互动,这些功能Uni-app官方也没提供SDK,怎么办呢?找IM老大腾讯云啊,今天我们就在Uni-app中把腾讯云即时通讯TXIM ...
- htmlunit设置只采集html,取消对css,javascript支持
引入htmlunit依赖 <!-- https://mvnrepository.com/artifact/net.sourceforge.htmlunit/htmlunit --> < ...
- C语言获取文件大小(字节)
代码 核心代码 FILE *pfile = nullptr; int ret = fopen_s(&pfile, str.c_str(), "rb"); /// 0 = 打 ...
- 【LeetCode】145. Binary Tree Postorder Traversal 解题报告 (C++&Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 迭代 日期 题目地址:https://leetc ...
- 【LeetCode】486. Predict the Winner 解题报告(Python)
[LeetCode]486. Predict the Winner 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: ht ...
- 【LeetCode】495. Teemo Attacking 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- 【剑指Offer】序列化二叉树 解题报告(Python)
[剑指Offer]序列化二叉树 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interviews 题目 ...