1.AQS核心思想
如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。
2. 主要原理
AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改
image.png
3. 阻塞和唤醒
获取不到锁的时候,进入阻塞队列,park在自身线程。一个线程执行完成后会释放锁,unpark排在队列的第一个线程,不会有惊群效应。park和unpark主要是用来阻塞和唤醒线程。
4. park unpark notify wait condition
image.png1.park()/unpark()底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的Semaphore,只不过这个信号量在重复执行unpark()的时候也不会再增加许可证,最多只有一个许可证
2.在java中,线程间的通信可以使用wait、notify、notifyAll来进行控制。wait进入synchronize获得锁之后释放锁等待唤醒。notify获得锁后释放锁,唤醒等待的线程。https://www.jianshu.com/p/f7d4819b7b24
3.wait()、notify()和notifyAll()是基于synchronized
Condition是基于Lock的。
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。阻塞队列实际上是使用了Condition来模拟线程间协作。
- 因为notify是唤醒所有等待的线程,唤醒的可能不是我们想要唤醒的线程。如果需要精准唤醒某一类线程,应该使用condition。参考:https://www.jianshu.com/p/d3214bd34a2d
- condition 详解参考:https://www.jianshu.com/p/58651d446e03
5. 独占锁,共享锁
AQS定义两种资源共享方式
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
* 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
* 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
1.ReentrantReadWriteLock是ReadWriteLock接口的实现类。ReadWriteLock接口的核心方法是readLock(),writeLock()。实现了并发读、互斥写。但读锁会阻塞写锁,是悲观锁的策略
ReentrantReadWriteLock也是通过AQS来实现锁的,但是ReentrantReadWriteLock有两把锁:读锁和写锁,它们保护的都是同一个资源,那么如何用一个共享变量来区分锁是写锁还是读锁呢?答案就是按位拆分。
由于state是int类型的变量,在内存中占用4个字节,也就是32位。将其拆分为两部分:高16位和低16位,其中高16位用来表示读锁状态,低16位用来表示写锁状态。当设置读锁成功时,就将高16位加1,释放读锁时,将高16位减1;当设置写锁成功时,就将低16位加1,释放写锁时,将第16位减1
- StampedLock是java8在java.util.concurrent.locks新增的一个API 。
该类是一个读写锁的改进,它的思想是读写锁中读不仅不阻塞读,同时也不应该阻塞写:在读的时候如果发生了写,则应当重读而不是在读的时候直接阻塞写!参考:https://duanguangguang.github.io/2018/12/31/concurrent/stampedlock/ - Semaphore(信号量)它通过 new Semaphore(permits) 来进行创建,permits 表示同一时间可以执行多少个线程。
使用 acquire 来获得许可,通过 release 来释放许可。在同一时间只允许 permits 个线程同时运行。初始5个许可,acquire一次state--,如果state为小数则进入等待。release相反。 - CountDownLatch 内部维护了一个计数器,当计数器不为 0 的时候调用其 await() 可以进行阻塞,每次使用 countDown() 计数器值 - 1,当计数器值为 0 的时候,所阻塞的线程从 await() 返回
利用这个特性我们可以用来合并多个线程最终的结果,或者以此来模拟并发请求调用等等,如下并发请求代码 参考https://juejin.cn/post/6844904032109068295 - CyclicBarrier 它可以实现让一组线程等待至某个状态之后再全部同时执行https://www.jianshu.com/p/4ef4bbf01811, 调用await方法,state--。 发现state不为0,则在condition上等待。 最后一个线程把state减为0的时候,唤醒所有在condition等待的线程。
- futureTask实现原理:https://zhuanlan.zhihu.com/p/40047276
参考文档:
clh队列:https://baiyp.ren/CLH%E9%98%9F%E5%88%97%E9%94%81.html
AQS原理:https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
AQS详解:https://pdai.tech/md/java/thread/java-thread-x-lock-AbstractQueuedSynchronizer.html
网友评论