线程池

Execuror

ThreadPoolExecuror

Callable和Future

Scheduled和ExecutorService

线程池配置

线程池监控

并发工具类

CyclicBarrier(屏障拦截)

让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门有被屏障拦截的线程才会继续运行。

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrier- Action),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景

与CountDownLatch异同

CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。

CyclicBarrier
加计数方式
计数达到指定值时释放所有等待线程
计数达到指定值时,计数置为0重新开始
调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
不可重复利用

CountDownLatch(数量执行)

CountDownLatch允许一个或多个线程等待其他线程完成操作

假如有这样一个需求:我们需要解析一个Excel里多个sheet的数据,此时可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用
join()方法

与CyclicBarrier异同

CountDownLatch的计数器只能使用一次

CountDownLatch
减计数方式
计算为0时释放所有等待的线程
计数为0时,无法重置
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响
不可重复利用

Sempahore(信号量)

信号量是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源

CAS(Compare And Swap比较交换)

Compare And Swap 即比较交换

比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值

1
2
3
4
5
6
7
8
int cas(long *addr, long old, long new)
{
/* 原子性操作. */
if(*addr != old)
return 0;
*addr = new;
return 1;
}

CAS开销

JVM对CAS的支持

在JDK1.5之前,如果不编写明确的代码就无法执行CAS操作
在JDK1.5中引入了底层的支持,在intlong对象的引用等类型上都公开了CAS的操作

AtomicXXX实现用了乐观锁技术,调用了sun.misc.Unsafe类库里面的 CAS算法,用CPU指令来实现无锁自增。所以,AtomicLong.incrementAndGet的自增比用synchronized的锁效率倍增。

1
2
3
4
5
6
7
public final boolean compareAndSet(long expect, long update) {
// 字段的偏移量
// Unsafe只有根加载器加载的类才能调用,如果想获得unsafe对象只能通过反射的方式获得
valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
// 操作对象,偏移量,预期值,修改值
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}

CAS使用时机

在轻度到中度的争用情况下,非阻塞算法的性能会超越阻塞算法,因为 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操作。

ABA

实现原理

ConcurrentHashMap的实现原理

ConcurrentLinkedQueue实现方法

参考

SPI 服务提供发现机制

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。

类图中,接口对应定义的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。

SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中;实现位于独立的包中。

当接口属于实现方的情况,实现方提供了接口和实现,这个用法很常见,属于API调用。我们可以引用接口来达到调用某实现类的功能。