让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
CyclicBarrier
默认的构造方法是CyclicBarrier(int parties)
,其参数表示屏障拦截的线程数量,每个线程调用await方法告CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier
还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrier- Action)
,用于在线程到达屏障时,优先执行barrierAction
,方便处理更复杂的业务场景
CyclicBarrier
的计数器可以使用reset()
方法重置。所以CyclicBarrier
能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。
CyclicBarrier 加计数方式 计数达到指定值时释放所有等待线程 计数达到指定值时,计数置为0重新开始 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 不可重复利用
CountDownLatch
允许一个或多个线程等待其他线程完成操作。假如有这样一个需求:我们需要解析一个Excel里多个sheet的数据,此时可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用
join()方法
CountDownLatch
的计数器只能使用一次
CountDownLatch 减计数方式 计算为0时释放所有等待的线程 计数为0时,无法重置 调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 不可重复利用
信号量是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。
1 |
int cas(long *addr, long old, long new) |
在JDK1.5之前,如果不编写明确的代码就无法执行CAS操作
在JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作
AtomicXXX实现用了乐观锁技术,调用了sun.misc.Unsafe类库里面的 CAS算法,用CPU指令来实现无锁自增。所以,AtomicLong.incrementAndGet的自增比用synchronized的锁效率倍增。
1 |
public final boolean compareAndSet(long expect, long update) { |
在轻度到中度的争用情况下,非阻塞算法的性能会超越阻塞算法,因为 CAS 的多数时间都在第一次尝试时就成功,而发生争用时的开销也不涉及线程挂起和上下文切换,只多了几个循环迭代。没有争用的 CAS 要比没有争用的锁便宜得多,而争用的 CAS 比争用的锁获取涉及更短的延迟。
ABA问题
因为
CAS
需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值 原来是A,变成了B,又变成了A,那么使用CAS
进行检查时会发现它的值没有发生变化,但是实际上却变化了解决思路就是使用版本号。在变量前面追加版本号,每次变量更新的时候把版本号+1,那么A->B->A 就会变成1A->2B->3A。从JDK1.5 开始,JDK的Atomic包里提供了一个类Atomic包里提供了一个AtomicStampedReference来解决ABA问题
循环时间长,开销大
自旋
CAS
如果长时间不成功,会给CPU
带来非常大的执行开销
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环
CAS
的方式来保证原子操作,但是对多个共享变量操作时,循环CAS
就无法保证操作的原子
性,这个时候就可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a
,合并一下ij=2a,然后用CAS
来操作ij。从Java 1.5开始,JDK提供了AtomicReference
类来保证引用对之间的原子性,就可以把多个变量放在一个对象里来进行CAS
操作。
java字节码
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
类图中,接口对应定义的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。
SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中;实现位于独立的包中。
当接口属于实现方的情况,实现方提供了接口和实现,这个用法很常见,属于API调用。我们可以引用接口来达到调用某实现类的功能。
虚拟机参数
类加载器