拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AQS 对资源的共享方式

一、AQSAbstractQueuedSynchronizer核心概念1.1 AQS 定义的两种资源共享方式AQSAbstractQueuedSynchronizer是Java并发包中锁和同步器的基础框架它定义了两种资源共享方式1.1.1 Exclusive独占只有一个线程能执行如 ReentrantLock。又可分为公平锁和非公平锁ReentrantLock 同时支持两种锁。公平锁 vs 非公平锁下面来看 ReentrantLock 中相关的源代码ReentrantLock 默认采用非公平锁因为考虑获得更好的性能通过 boolean 来决定是否用公平锁传入 true 用公平锁。公平锁的 lock 方法非公平锁的 lock 方法总结公平锁和非公平锁只有两处不同公平锁和非公平锁就这两点区别如果这两次 CAS 都不成功那么后面非公平锁和公平锁是一样的都要进入到阻塞队列等待唤醒。相对来说非公平锁会有更好的性能因为它的吞吐量比较大。当然非公平锁让获取锁的时间变得更加不确定可能会导致在阻塞队列中的线程长期处于饥饿状态。1.1.2 Share共享1.2 AQS 底层使用了模板方法模式AQS 使用了模板方法模式自定义同步器时需要重写下面几个 AQS 提供的模板方法二、常用并发工具类详解2.1 Semaphore信号量- 允许多个线程同时访问synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore信号量可以指定多个线程同时访问某个资源。示例代码如下执行 acquire 方法阻塞直到有一个许可证可以获得然后拿走一个许可证每个 release 方法增加一个许可证这可能会释放一个阻塞的 acquire 方法。然而其实并没有实际的许可证这个对象Semaphore 只是维持了一个可获得许可证的数量。Semaphore 经常用于限制获取某种资源的线程数量。当然一次也可以一次拿取和释放多个许可不过一般没有必要这样做除了 acquire 方法之外另一个比较常用的与之对应的方法是 tryAcquire 方法该方法如果获取不到许可就立即返回 false。2.1.1 Semaphore 的公平模式与非公平模式Semaphore 有两种模式公平模式和非公平模式。公平模式调用 acquire 的顺序就是获取许可证的顺序遵循 FIFO非公平模式抢占式的。Semaphore 对应的两个构造方法如下这两个构造方法都必须提供许可的数量第二个构造方法可以指定是公平模式还是非公平模式默认非公平模式。补充Semaphore与CountDownLatch一样也是共享锁的一种实现。它默认构造AQS的state为permits。当执行任务的线程数量超出permits那么多余的线程将会被放入阻塞队列Park并自旋判断state是否大于0。只有当state大于0的时候阻塞的线程才能继续执行此时先前执行任务的线程继续执行release方法release方法使得state的变量会加1那么自旋的线程便会判断成功。如此每次只有最多不超过permits数量的线程能自旋成功便限制了执行任务线程的数量。2.2 CountDownLatch倒计时器2.2.1 CountDownLatch 的两种典型用法2.2.2 CountDownLatch 的使用示例上面的代码中我们定义了请求的数量为 550当这 550 个请求被处理完成之后才会执行System.out.println(finish);与 CountDownLatch 的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用 CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞直到其他线程完成各自的任务。其他 N 个线程必须引用闭锁对象因为他们需要通知CountDownLatch对象他们已经完成了各自的任务。这种通知机制是通过CountDownLatch.countDown()方法来完成的每调用一次这个方法在构造函数中初始化的 count 值就减 1。所以当 N 个线程都调用了这个方法count 的值等于 0然后主线程就能通过 await() 方法恢复执行自己的任务。注意CountDownLatch 的await()方法使用不当很容易产生死锁比如我们上面代码中的 for循环改为这样就导致 count 的值没办法等于 0然后就会导致一直等待。2.2.3 CountDownLatch 的不足CountDownLatch 是一次性的计数器的值只能在构造方法中初始化一次之后没有任何机制再次对其设置值当 CountDownLatch 使用完毕后它不能再次被使用。2.2.4 CountDownLatch 常见面试题解释一下 CountDownLatch 概念CountDownLatch 和 CyclicBarrier 的不同之处给出一些 CountDownLatch 使用的例子CountDownLatch 类中主要的方法2.3 CyclicBarrier循环栅栏CyclicBarrier 和 CountDownLatch 非常类似它也可以实现线程间的技术等待但是它的功能比CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CountDownLatch的实现是基于AQS的而CyclicBarrier是基于 ReentrantLockReentrantLock也属于AQS同步器和 Condition 的。CyclicBarrier 的字面意思是可循环使用Cyclic的屏障Barrier。它要做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties)其参数表示屏障拦截的线程数量每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障然后当前线程被阻塞。2.3.1 CyclicBarrier 的构造函数其中parties 就代表了有拦截的线程的数量当拦截的线程数量达到这个值的时候就打开栅栏让所有线程通过。2.3.2 CyclicBarrier 的应用场景2.3.3 CyclicBarrier 的使用示例示例 1运行结果如下可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候await 方法之后的方法才被执行。另外CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction)用于在线程到达屏障时优先执行barrierAction方便处理更复杂的业务场景。示例代码如下运行结果如下2.3.4 CyclicBarrier 源码分析dowait(false, 0L)总结CyclicBarrier 内部通过一个 count 变量作为计数器count 的初始值为 parties 属性的初始化值每当一个线程到了栅栏这里了那么就将计数器减一。如果 count 值为 0 了表示这是这一代最后一个线程到达栅栏就尝试执行我们构造方法中输入的任务。2.3.5 CyclicBarrier 和 CountDownLatch 的区别下面这个是国外一个大佬的回答CountDownLatch 是计数器只能使用一次而 CyclicBarrier 的计数器提供 reset 功能可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从 jdk 作者设计的目的来看javadoc 是这么描述它们的对于 CountDownLatch 来说重点是一个线程多个线程等待而其他的 N 个线程在完成某件事情之后可以终止也可以等待。而对于 CyclicBarrier重点是多个线程在任意一个线程没有完成所有的线程都必须等待。CountDownLatch 是计数器线程完成一个记录一个只不过计数不是递增而是递减而 CyclicBarrier 更像是一个阀门需要所有线程都到达阀门才能打开然后继续执行。三、其他重要锁机制3.1 ReentrantLock 和 ReentrantReadWriteLockReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外需要注意的是读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读所以在读操作远大于写操作的时候 读写锁就非常有用了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门