多线程-- 八.J.U.C之AQS
AQS
一.AQS的概念:
lock包下有三个笼统的类:
AbstractOwnableSynchronizer
AbstractQueuedLongSynchronizer
AbstractQueuedSynchronizer
通常的,AbstractQueuedSynchronizer的简称为AQS。一般我们叫AQS抽象队列同步器。
AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它
它是J.U.C的核心。
二.AQS的设计:
1.使用Node实现FIFO(First in First out)队列,可以用于构建锁或者其它同步装置的基础框架。
2.利用了一个Int类型表示状态。
3.使用方法是继承。子类通过继承并通过实现它的方法管理其状态(acquire和release)的方法操纵状态。
4.可以同时实现排它锁和共享锁模式。(独占,共享)
三.AQS的常用同步组件:
1.CountDownLatch
2.Semaphore
3.CyclicBarrier
4.ReentrantLock
5.Condition(基于ReentrantLock的)
6.FutureTask(它不是AbstractQueuedSynchronizer类的子类)
1.CountDownLatch
它是一个同步辅助类,通过它可以完成类似于阻塞当前线程的功能,换句话说,就是一个或多个线程一直等待,直到其它线程执行的操作完成。
它用了一个给定的计数器来进行初始化,该计数器的操作是原子性的,也就是说,同时只能有一个线程去操作计数器。
调用该类await方法的线程,会一直处于阻塞状态,直到其它线程调用countDown方法,使当前计数值的值变为0。
每次调用countDown方法的时候,计数器的值会减一。当计数器的值减到0的时候,所有调用await方法而处于等待状态的线程会继续往下执行。这操作只能出现一次,因为计数器是不能重置的。
典型应用场景:并行计算。
举例:
CountDownLatch countDownLatch = new CountDownLatch(200);
200表示需要等待执行完毕的线程数量。
结果就是,finish是在所有线程执行完毕之后执行的。
也可以有复杂的情况,就是每个任务我都给指定时间,超过指定时间没做完,那也不管了。这也好弄,就是修改await方法
第一个参数是数值,第二个参数是单位。
2.Semaphore
信号量。它可以控制并发访问的线程个数。和CountDownLatch使用有些类似,也提供了两个核心方法。acquire和release.
acquire方法是让它获取一个许可,如果没有,就等待; release方法是执行完成后,释放一个许可出来。
使用场景:常用于仅能提供有限访问的资源,比如数据库。数据库连接数假设只有20,而我们应用的并发数可能会远远大于这些。如果同时对数据库进行操作,有可能会出现因为无法获取数据库连接,而导致异常。这个时候我们就可以通过Semaphore控制并发数
举例:
上图只是获取一个许可,如果我们有需求,需要拿到多个许可才可以执行怎么办?
答:也是在方法里面加参数即可:
以上图为例,假如我们想,超过3个并发,剩下的不执行了,丢弃,那么怎么办?
答:用semaphore.tryAcquire()方法即可。
3.CyclicBarrier
也是一个同步辅助类。他允许一组线程相互等待直到某个公共的屏障点。
通过它可以完成多个线程之间相互等待,只有当每个线程都准备就绪后才能各自继续往下执行后面的操作。
和CountDownLatch相似的地方上是,它也是通过计数器实现的。
代码举例实现:
CyclicBarrier barrier = new CyclicBarrier(5); 这句代码告诉我们当前有5个线程要同步等待。
barrier.await(); 当达到括号里给定的数目的时候,这段代码后面的部分就可以执行了。
运行结果为:
同样,这个await方法也是可以传参的,跟上面一样,也是传入时间,意思就是让它只等待多少时间。
而且,在CyclicBarrier barrier = new CyclicBarrier(5)代码里面,也可以传入第二个参数runnable接口。例:
CyclicBarrier barrier = new CyclicBarrier(5,()->{
log.info("test running");
});
意思是,在达到屏障点的时候,优先执行里面的代码。
4.ReentrantLock 与 锁
Java里主要分为两类锁,一类是synchronized关键字修饰的锁;一类是J.U.C里提供的锁。
一.ReentrantLock(可重入锁)和synchronized的区别
①.可重入性
②.锁的实现
synchronized关键字是依赖JVM实现的,ReentrantLock是JDK实现的。
③.性能区别
在synchronized关键字优化以前,性能差很多。但是引入了轻量级锁之后,两者性能差不多了。
④.功能区别
便利性:synchronized用起来比较方便,是由编译器决定锁的加锁和释放,不会造成死锁,JVM自动释放。而ReentrantLock需要我们手工加锁释放锁。为了避免忘记释放造成死锁,最好在finally中声明。
锁的细粒度和灵活度:ReentrantLock要优于synchronized。
二.ReentrantLock独有的功能
①.ReentrantLock可指定是公平锁还是非公平锁;而 synchronized只能是非公平锁。
公平锁:先等待的线程先获得锁。
②.ReentrantLock提供了Condition类,可以分组唤醒需要唤醒的线程;而synchronized要么随机唤醒一个,要么唤醒全部。
③.ReentrantLock提供了能够中断等待锁的线程的机制,lock.lockInterruptibly()
所以,如果想实现这三个功能的时候,是必须要使用ReentrantLock的。
代码演示:
在Lock lock = new ReentrantLock()创建锁对象时,可以传入参数。
源码如下:
默认传入的是个非公平锁。可以传入参数true false使其成为公平锁。
5.StampedLock
StampedLock控制锁有三种模式。写,读,乐观读。重点在乐观读上。
一个StampedLock状态是由版本和模式两个部分组成。锁获取方法返回的是一个数字,作为票据。0表示没有写锁,读锁上分为悲观锁和乐观锁。
对吞吐量有巨大改进,性能高一些。
举例: