今天面试的时候被问到synchronizer的底层实现,当时脑子里有图但是怎么也说不出来就很绝望,回来一看往年笔记果然还是漏了很多点以及答错的部分。还是对这段的代码理解得不够透彻,写文章梳理一下。
并发容器J.U.C(java.util.concurrent),存放了所有的并发类,包括并发工具、并发集合,锁等等。
JAVA之所以会有多线程问题,就在于JAVA内存模型(JMM)
多线程修改就会触发数据混乱。
解决方法:
1.JMM使用volatile保证部分的可见性,但是volatile的局限性在于只能在一写多读的情况下保证。
2.使用原子操作类(Atomic···)
这一类就基于CAS机制+自旋
一、AQS
AbstractQueuedSynchronizer,是并发容器J.U.C(java.util.concurrent)下locks包内的一个抽象类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列以及state(被volatile修饰)变量。底层实现的数据结构是一个双向链表。有两种模式:独占式和共享式
1.
![AQS关键工程类截图](https://img-blog.csdnimg.cn/e8c6dd1faaea4d0281d60b524dc4515b.png)
2.
![AQS关键工程类截图——AbstractQueuedSynchronizer](https://img-blog.csdnimg.cn/ab172532f1034dd4af211f879703840c.png)
详细连接:AQS分析(含底层)http://t.csdn.cn/RUc49
二、CAS
CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过设定条件加锁的方式来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。
只能有一个变量
解决方案:使用AtomicReferce
ABA问题:看似没有被改过的值实际上被修改过。比如 i= 1 -> i=2 -> i=1。
解决方案:使用 AtomicStampedReference这个类,可以增加时间戳
CPU开销大
三、synchronizer
四、ReentrantLock
是基于AQS实现的。在lock()方法中,执行了Sync(抽象类)的lock()方法,而Sync是继承了AQS。且Sync有两个实现子类,一个FairSync(公平锁)一个NonfairSync(非公平锁)。
1.公平锁
2.非公平锁
多了一个属性判断,直接尝试CAS,尝试将state变为1,如果成果就获取资源。如果失败则进行下一步。
第一步:尝试拿锁(
tryAcquire(int arg);
第二步:查看addWaiter。没拿到锁,需要排队;
addWaiter();
第三步:查看acquireQueued。挂起线程以及被唤醒后续步骤。