随笔读并发包源码
最近在读并发包源码,喜欢的就记录下。
源码解析系列===》写的挺不错,强烈推荐看源码时候对着他的讲解看,能省好多事。(貌似得翻墙。。。。)
- Excutor
ThreadPoolExcutor是线程池实现的核心,可以比对下面这篇文章一起看,作者总结的不错。
深度解读 java 线程池设计思想及源码实现
如果你读过aqs的实现+reentrantLock源码,这ThreadPoolExcutor就简单多了,感觉Doug Lea大神先有了个状态state+队列的idea,创造了aqs,完了创造了五颜六色的世界。
ThreadPoolExcutor里面真正管理执行线程的是Worker这个内部类,他继承了aqs,相当于对当前线程做了增强,有了锁特性、
可以hang在BlockingQueue的take方法上去等待任务、执行的时候检查线程池的状态,你通过excute方法区执行线程的时候,其实是交给worker去处理。
面试的时候经常会问ThreadPoolExcutor线程池的corePoolSize、keepAliveTime、RejectedExecutionHandler、workQueue、maximumPoolSize几个参数的意思啥的,让你说说线程池运行的规则,网上会有一堆文章,你临时补补呢,也能说个89不离10,其实呢,静下心来,抱着学习的目的看看源码,也挺有意思的,记忆还深刻。
线程池的线程实现了Cyclic Work Distribution=== 循环工作分配模式
-
DelayQueue
子类要实现getDelay、compareTo方法,就是你要告诉DelayQueue以什么规则去延时,这个是你自己实现的事情。
他的核心是具有ReentrantLock、Condition的,你读读他的take方法,如果你有aqs的基础,你就能知道机制其实就是一堆线程排队,互相唤醒去比较下我的时间到了没有,到了我就出队了。中间还应用了Leader-Follower模型,就是只有一个线程干活,其他的发现自己不是Leader,即使被唤醒了他还得排队去。
也就是说,延时消息其实并不能准确的到点被唤醒,是有时间差的。 -
ForkJoinPool
恶补文章:
1.1 [Java多线程进阶(四三)—— J.U.C之executors框架:Fork/Join框架(1) 原理] 有2篇
1.2 JUC源码分析-线程池篇(四):ForkJoinPool - 1 2篇
1.3 线程基础:多任务处理(12)——Fork/Join框架(基本使用) 一个系列,有5篇
从头到尾看对着源码看文章吧,感觉没一星期扒不明白。---我暂时放弃一下。 -
ConcurrentHashMap
1.1 Java多线程进阶(二三)—— J.U.C之collections框架:ConcurrentHashMap(1) 原理
1.2 Java多线程进阶(二四)—— J.U.C之collections框架:ConcurrentHashMap(2) 扩容
1.3 Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
这部分好有意思:
ConcurrentHashMap在处理rehash的时候,并不会重新计算每个key的hash值,而是利用了一种很巧妙的方法。我们在上一篇说过,ConcurrentHashMap内部的table数组的大小必须为2的幂次,原因是让key均匀分布,减少冲突,这只是其中一个原因。另一个原因就是:
当table数组的大小为2的幂次时,通过key.hash & table.length-1这种方式计算出的索引i,当table扩容后(2倍),新的索引要么在原来的位置i,要么是i+n。
我们来看个例子:
image.png
上图中:
扩容前,table数组大小为16,key1和key2映射到同一个索引5;
扩容后,table数组的大小变成 2*16=32 ,key1的索引不变,key2的索引变成 5+16=21。
而且还有一个特点,扩容后key对应的索引如果发生了变化,那么其变化后的索引最高位一定是1(见扩容后key2的最高位)。
这种处理方式非常利于扩容时多个线程同时进行的数据迁移操作,因为旧table的各个桶中的结点迁移不会互相影响,所以就可以用“分治”的方式,将整个table数组划分为很多部分,每一部分包含一定区间的桶,每个数据迁移线程处理各自区间中的结点,对多线程同时进行数据迁移非常有利,后面我们会详细介绍。
- CompletableFuture
1.1 通过实例理解 JDK8 的 CompletableFuture
分治思想