线程池

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实现方法

参考

Java 并发集合

队列

BlockingQueue

BlockingQueue即阻塞队列

实现方法

  • add(E): boolean

    插入数据满了抛出IllegalStateException

  • offer(E): boolean

    插入数据, 不抛出异常(如果满了),返回boolean

  • offer(E, long, TimeUnit):boolean

  • put(E):void

    堵塞插入数据

  • take():E

    取出并删除头部数据, 阻塞直到获取

  • poll(long, TimeUnit):E

    取出并删除头部数据, 可设置可用的时间

  • remove(Object): boolean

    删除一个实例

  • contains(Object): boolean

    实例是否在队列

  • remainingCapacity(): int

    剩余插入数量

  • drainTo(Collection): int

    移除队列, 加入到collection集合中

  • drainTo(Collection, int): int

    移除队列, 加入到collection集合中, 设置最大值

实现类

ArrayBlockingQueue

组实现的有界阻塞队列。该队列命令元素FIFO(先进先出)

队列中的锁是没有分离的,即生产和消费用的是同一个锁

相对于Linked内存开销较小(无须动态申请存储空间)

LinkedBlockingQueue

基于链表的阻塞队列

队列中的锁是分离的(采用两把锁),即生产用的是putLock,消费是takeLock

队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE, 可能会内存溢出

DelayQueue

一个无边界的存放<.Delayed>元素的支持延迟获取元素的阻塞队列

内部使用<.PriorityQueue>存储元素, 元素必须实现<.Delayed> 接口

创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素

PriorityBlockingQueue

一个无边界可以使用排序规则(权重)的阻塞队列。

每个元素要实现compareTo方法因此不支持null

会根据优先级将优先级最小的最先取出

SynchronousQueue

不会为队列中元素维护存储空间

它维护一组线程,这些线程在等待着把元素加入或移出队列。

使用场景:

Executors.newCachedThreadPool()

##BlockingDeque

继承BlockingQueue

LinkedBlockingDeque

基于链表的双端阻塞队列

TransferQueue

继承BlockingQueue

生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)

消费者的消费能力将决定生产者产生消息的速度

实现方法:

  • transfer(E): void

    当前存在一个正在等待获取的消费者线程

    否则将元素e插入到队列尾部,并且当前线程进入阻塞状态,直到有消费者线程取走该元素

  • tryTransfer(E): boolean

    不进行堵塞, 返回boolean状态

  • tryTransfer(E, long, TimeUnit):boolean

    堵塞超时,返回boolean状态

  • hasWaitingConsmer():boolean

  • getWaitingCOnsumerCount():int

ConcurrentMap

提供线程安全和原子性保证的map

ConcurrentHashMap

与HashMap不同的是,ConcurrentHashMap中多了一层数组结构,由Segment和HashEntry两个数组组成。其中Segment起到了加锁同步的作用,而HashEntry则起到了存储K.V键值对的作用。

在多线程中,每一个Segment对象守护了一个HashEntry数组,当对ConcurrentHashMap中的元素修改时,在获取到对应的Segment数组角标后,都会对此Segment对象加锁,之后再去操作后面的HashEntry元素,这样每一个Segment对象下,都形成了一个小小的HashMap,在保证数据安全性的同时,又提高了同步的效率。只要不是操作同一个Segment对象的话,就不会出现线程等待的问题!

ConcurrentNavigableMap

继承ConcurrentMap

多线程

Atomic 原子

AtomicBoolean

AtomicInteger

AtomicIntegerArray

AtomicIntegerFieldUpdater

Long…

AtomicMarkableReference

AtomicReference

AtomicReferenceArray

AtomicReferenceFieldUpdater

AtomicStampedReference

DoubleAccumulator

DoubleAdder

LongAccumulator

LongAdder

Striped64

Locks 锁

Locks

ReentrantLock

Condition

ReadWriteLock

ReentrantReadWriteLock

LockSupport

StampedLock

Java并发

CPU多级缓存

CPU多级缓存的出现是缓解CPU和内存之间速度的不匹配问题

多核CPU的结构与单核相似,但是多了所有CPU共享的L3三级缓存。在多核CPU的结构中,L1和L2是CPU私有的,L3则是所有CPU核心共享的。

意义:

  1. 时间局部性:

    某个数据被访问,那么在不久的将来它很可能被再次访问

  2. 空间局部性:

    某个数据被访问,那么它相邻的数据很快也可能被访问

缓存一致性(MESI):

在多核CPU中,内存中的数据会在多个核心中存在数据副本,某一个核心发生修改操作,就产生了数据不一致的问题。而一致性协议正是用于保证多个CPU cache之间缓存共享数据的一致。

synchronized

可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作)

可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代volatile功能)

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁

Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现, 无论是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同步代码块)还是隐式同步都是如此。在 Java 语言中,同步用的最多的地方可能是被 synchronized 修饰的同步方法。同步方法 并不是由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现

在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。

庆幸的是在Java 6之后Java官方对从JVM层面对synchronized较大优化,所以现在的synchronized锁效率也优化得很不错了,Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁

syncgronized 修饰

修饰代码块,通过对对象引用monitor计算器加减,实现锁

1
2
3
4
5
6
3: monitorenter  //进入同步方法
//..........省略其他
15: monitorexit //退出同步方法(正常退出)
16: goto 24
//省略其他.......
21: monitorexit //退出同步方法(异常退出)

锁的状态总共有四种,无锁状态偏向锁轻量级锁重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级

https://blog.csdn.net/javazejian/article/details/72828483