java线程池
死锁
原因
- 互斥,共享资源 X 和 Y 只能被一个线程占用;
- 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候, 不释放共享资源 X;
- 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
- 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占 有的资源,就是循环等待。
如何更好并发(参考这篇)
谁去做分(理解的不是很清晰,先记下,以后更加理解再来说)
- 对于简单的并行任务,你可以通过“线程池 +Future”的方案来解决;
- 如果任务之间有聚合关系,无论是 AND 聚合还是 OR 聚合,都可以通过 CompletableFuture 来解决;
- 批量的并行任务,则可以通过 CompletionService 来解决。
任务本身分
- 分对并发的任务进行分治---fork/join。
分治任务模型可分为两个阶段:一个阶段是任务分解,也就是将任务迭代地分解为子任务,直至子任务可以直接计算出结果;另一个阶段是结果合并,即逐层合并子任务的执行结果,直至获得最终结果。
Fork/Join 是一个并行计算的框架,主要就是用来支持分治任务模型的,这个计算框架里的 Fork 对应的是分治任务模型里的任务分解,Join 对应的是结果合并。Fork/Join 计算框架主要包含两部分,一部分是分治任务的线程池 ForkJoinPool,另一部分是分治任务 ForkJoinTask。这两部分的关系类似于 ThreadPoolExecutor 和 Runnable 的关系,都可以理解为提交任务到线程池,只不过分治任务有自己独特类型 ForkJoinTask。
ForkJoinTask 是一个抽象类,它的方法有很多,最核心的是 fork() 方法和 join() 方法,其中 fork() 方法会异步地执行一个子任务,而 join() 方法则会阻塞当前线程来等待子任务的执行结果。ForkJoinTask 有两个子类——RecursiveAction 和 RecursiveTask,通过名字你就应该能知道,它们都是用递归的方式来处理分治任务的。这两个子类都定义了抽象方法 compute(),不过区别是 RecursiveAction 定义的 compute() 没有返回值,而 RecursiveTask 定义的 compute() 方法是有返回值的。这两个子类也是抽象类,在使用的时候,需要你定义子类去扩展。
在Fork/Join里面有一个叫做工作窃取的概念,某个线程可以从其他队列中去获取任务来执行。假设现在有两个线程来处理一个任务,这个任务分割成了8个小任务,分别放到了两个队列当中。线程1负责A队列,线程2负责B队列,每个线程从队列得到头部和尾部都可以获取任务来执行,当线程1执行完了队列A的所有任务,它还可以从队列B的尾部(避免竞争,所以从尾部窃取)来窃取任务来执行。这样的好处可以充分利用线程,缩短任务执行时间,提高效率。缺点就是如果只有一个任务,还是会存在两个线程取竞争,并且窃取的算法也会消耗一定资源。
互斥,怎么去做分
- 锁
ConcurrentHashMap满足两种情况会扩容:
1. 当新增节点之后,所在的链表元素个数达到了阈值8,并且数组长度小于阈值64的时候
2. 在调用 addCount 方法增加集合元素计数后发现当前集合元素个数到达扩容阈值时就会触发扩容
当线程处于扩容状态下,其他线程对集合进行数据操作时会参与帮助扩容,扩容带来的数据迁移
f = tabAt ( tab , i = ( n-1 ) & hash)) == null
put数据的时候通过这个方法来定位到数组下标