java学习-并发-AQS
AQS 实际就是一个双向队列
@Slf4j public class AbstractQueueDemo { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(AbstractQueueDemo::action); executorService.submit(AbstractQueueDemo::action); executorService.shutdown(); } public static final Lock LOCK = new ReentrantLock(); public static void action() { String name = Thread.currentThread().getName(); try { /** * 当并发线程执行时大概的时序图 * 存在两个线程 pool-1-thread-1 和 pool-1-thread-2 * 当第一个线程 pool-1-thread-1 获得到锁时, 由于System.in.read() 的原因导致当线程被阻塞等待控制台输入; * 与此同时,第二个线程 pool-1-thread-2 需要等待 pool-1-thread-1 释放当前持有的锁资源,因此会调用java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node, int, boolean, boolean, boolean, long)操作, * 第一次循环:由于当前操作是第一次执行,会首先为当前线程创建一个排他节点(node0) * 继续下一次循环,设置当前新创建的节点等待线程为当前线程,由于当前AQS双向链表尚未创建,此时会使用cas操作生成一个新的head节点(node1),并将tail节点指向当前新的node,对于当前操作的结果就是生成了一个新的节点,此时head和tail都指向同一个对象表示当前链表初始化生成结束; head <=> tail -> node1 * 继续下一次循环,由于此时当前head和tail 都是指向的相同的无意义节点,设置node0 的 prev 指向 node1,会执行cas操作设置当前aqs队列的tail指向node0,并设置node1 的 next 指向 node0,此时aqs的结构就变成了 head(node1) <-> tail(node0) * 继续下一次循环,由于当前自旋次数(spins是为了满足超时等待情况下的操作)不为0,当前node0 中的 status 为默认值 0,修改 node0 的status 为 1 * 继续下一次循环,此时就会进入最后一个else判断,首先会变更当前spins 的值为 1,默认是非超时等待此时会挂起当前线程(pool-1-thread-2),代码被阻塞到此处 * * 当线程pool-1-thread-1 释放锁时,会通知到当前被挂起的线程 pool-1-thread-2,此时pool-1-thread-2会从继续从上一次阻塞处的代码继续往后执行,修改当前node0的status 为 0, * 继续下一次循环,此时再去执行tryacquire 就会获取锁成功,如果当前节点为第一个节点(first == true),因此会修改当前节点作为head节点,并结束当前acquire方法 */ log.info("{}线程准备获取锁", name); LOCK.lock(); log.info("{}线程成功获取锁,并等待控制台输入", name); System.in.read();// 阻塞当前线程等待控制台输入 } catch (IOException e) { e.printStackTrace(); } finally {
/**
* 在当前线程释放锁时,首先会修改当前aqs 的 state 的状态, 并会调用 java.util.concurrent.locks.AbstractQueuedSynchronizer#signalNext(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node)来通过 head 节点获取head.next(node0), 如果node0 的status 不为0,则修改node0 的status 为 1, 并唤醒当前node0 的waiter(线程),使被唤醒的线程继续竞争锁
*/
LOCK.unlock(); log.info("{}线程释放锁", name); } } }
对于上述代码中的aqs 队列的过程进行画图分析(图片需要放大查看)

对于conditionObject 的理解:
conditionObject 实际就是为了实现线程通讯的条件变量; 对于conditionObject 中所有操作都必须要求是在lock情况下,否则会抛出 IllegalMonitorStateException;
对于conditionObject 中的 await / await(time) / signal / signalAll 的理解
await 操作实际会释放当前线程所持有的锁,并将当前线程挂起;
signal 操作实际是将当前condition对象所拥有的conditionNode属性值重新添加到当前 ReentrantLock 对象的AQS队列中 实现了加锁的操作; 然后在之后的unlock操作中会去通知当前AQS中的等待节点 从而实现线程唤醒操作; 对于signal操作并不具有唤醒线程的操作,而是通过后续的解锁操作实现了线程的唤醒
public static void conditionObjectDemo(){ Condition condition = LOCK.newCondition(); new Thread(()->{ try { LOCK.lock(); log.info("当前线程{}准备进入阻塞",Thread.currentThread().getName()); condition.await(); // 通过 conditionNode 的 status 来实现线程通信以及线程挂起设置,且会release 当前线程持有的锁 log.info("当前线程{}被唤醒",Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { LOCK.unlock(); } }).start(); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } LOCK.lock(); condition.signal();// 将当前condition 持有的 conditionNode 追加到当前AQS中 LOCK.unlock(); // 释放当前线程持有的锁,并唤醒当前aqs中的下一个node }

浙公网安备 33010602011771号