最近真的是对Java中的基础思想狠下功夫,因为面试一线大厂,这种基础简直是必问。上一节讲过CAS,这回我们来看AQS。
1、学习思路:从非公平锁进入源码,可以看到Sync extends AbstractQueuedSynchronizer,这个AbstractQueuedSynchronizer就是AQS。
2、有CAS还不够吗,为什么设计AQS?
CAS 只是保证原子性。而AQS却能保证并发线程的有序且安全。
Java作者是想把AQS做成并发编程的基础组件。显示锁、读写锁的源码中都有AQS,线程池中的Worker也使用了AQS。所以记住:AQS是并发编程中很基础很基础的组件。
3、理解AQS前,先了解下LockSupport和CLH队列锁
(1)LockSupport:提供了线程的阻塞和唤醒功能。是构建AQS组件的基础工具。
(2)CLH队列锁:一种思想。尝试获取锁的多个线程,当一个线程获取到锁,那么其他的线程要等待,这些等待的线程会被放入一个列表,如图所示:
每一个线程都会被包装成这样的一个节点:前驱节点的引用和当前节点的lock状态。
新来的线程会放到队列/链表尾部,新线程将自己包装成一个节点,节点中包含自身锁的状态和前驱节点的引用,而且线程节点一直在这个前驱节点的引用上一直自旋(监控),看前驱节点的lock状态是否改变,前驱借钱锁释放了,就会locked变为false。后面的节点监测到这个就会改状态。
4、真正了解AQS
AQS 是面向锁的开发者的,像我们平时要用到锁时直接使用Lock就好了,不会去再实现AQS。也就是说AQS时锁的源码中的思想,太底层了。
AQS是抽象类,里面几个方法没有实现,这是用了模板方法模式,这些实现放到子类中去实现。
对于锁,它是有状态的,必须有一个标志位来标志,而AQS中是使用了一个volatile int state表示,volatile保证线程间的可见性,所以其他等待的线程可以见到这个锁标志,AQS又用CAS通过对这个标志状态 由0 变1来完成锁获取锁释放的变化,保证了state的线程安全。这个标志叫state,非常重要的变量。
5、总结
AQS的数据结构:链表 + 状态标志
AQS = CLH队列锁 + CAS:
CLH队列锁:阻塞线程排队
CAS:保证锁的线程安全