2、线程的并发工具类
一、Fork-Join
体现了“分而治之”设计思想:将一个大问题分割为相同改的小问题,且小问题之间无关联
十大计算机经典算法:快速排序、堆排序、归并排序、二分查找、线性查找、深度优先、广度优先、Dijkstra、动态规划、朴素贝叶斯分类
其中快速排序、归并排序、二分查找都属于分而治之的思想
Fork-Join原理
Fork-Join框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join汇总。
Fork-Join的使用
我们要使用 ForkJoin 框架,必须首先创建一个 ForkJoin 任务。它提供在任务 中执行 fork 和 join 的操作机制,通常我们不直接继承 ForkjoinTask 类,只需要直接继承其子类。
public abstract class ForkJoinTask<V>
-
RecursiveAction,用于没有返回结果的任务
public abstract class RecursiveAction extends ForkJoinTask<Void>
-
RecursiveTask,用于有返回值的任务
public abstract class RecursiveTask<V> extends ForkJoinTask<V>
task 要通过 ForkJoinPool 来执行,使用 invoke或submit/execute 提交,两者的区别是:invoke 是同步执行,调用之后需要等待任务完成,才能执行后面的代码;submit/execute是异步执行。submit和execute区别:submit有返回值;execute无返回值。
-
compute()方法:
protected abstract V compute();//实现compute()方法做自己的事情
在我们自己实现的 compute 方法里,首先需要判断任务是否足够小,如果 足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用 invokeAll()方法时,又会进入 compute()方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。
-
join()方法:
汇总合并,使用 join 方法会等待子任务执行完并得到其结果。是个阻塞方法。
二、CountDownLatch的作用、应用场景和实战:
闭锁,CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch 是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1 (CountDownLatch.countDown()方法)。当计数器值到达0时,它表示所有的已经完成了任务,然后在闭锁上等待 CountDownLatch.await()方法的线程就可以恢复执行任务。
应用场景:
实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。 例如,我们想测试一个单例类。如果我们创建一个初始计数为 1 的 CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成 测试。我们只需调用 一次 countDown()方法就可以让所有的等待线程同时恢复执行。
三、CAS(Compare And Swap)
实现原子操作可以使用锁机制。但synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁是相对于一些需要复杂操作。而一些简单的应用场景,如加减操作,使用同步锁就显得不那么灵活了。
基本原理:
利用了现代处理器都支持的CAS指令,循环这个指令,直至成功为止
基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。循环CAS就是在一个循环里不断的做cas操作,直到成功为止
CAS实现原子操作的三大问题
-
ABA问题
因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本戳。
-
循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
-
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁
Jdk中相关原子操作类的使用
- 更新基本类型类:AtomicInteger 、AtomicBoolean、AtomicLong
- 更新数组类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
-
更新引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference
AtomicReference解决的是只能保证一个共享变量的原子操作,将多个变量组成一个对象进行操作;
AtomicStampedReference和AtomicMarkableReference差不多,都是利用版本戳的形式记录了每次改变以后的版本号。不同点是,前者使用了pair的intstamp作为计数器使用,关心的是动过几次。而后者的pair使用的是boolean,关心的是有没有被人动过。