程池ThreadPoolExecutor类的使用
1.使用线程池的好处?
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2.ThreadPoolExecutor的使用
A.线程池的创建
我们可以通过java.util.concurrent.ThreadPoolExecutor来创建一个线程池。
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
创建线程池需要的参数介绍:
-
corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
-
runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
可以通过调用Executors
类的static newFixedThreadPool()
方法获得一个固定线程池。
语法
ExecutorService fixedPool = Executors.newFixedThreadPool(2);
Java
其中,
- 最多
2
个线程将处于活动状态。 - 如果提交了两个以上的线程,那么它们将保持在队列中,直到线程可用。
- 如果一个线程由于执行关闭期间的失败而终止,则执行器尚未被调用,则创建一个新线程。
- 线程会一直存在,直到池关闭。
多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么ThreadLocal可以很方便的帮你做到这一点。
先来看一下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
运行结果:
B:48
A:32
即:线程A与线程B中ThreadLocal保存的整型变量是各自独立的,互不相干,只要在每个线程内部使用set方法赋值,然后在线程内部使用get就能取到对应的值。
把这个示例稍微变化一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
把ThreadLocal赋值的地方放在了MyRunnable的构造函数中,然后在run方法中读取该值,看下结果:
main:1
main:47
A:null
B:null
思考一下:为什么会这样? MyRunnable的构造函数是由main主线程调用的,所以TheadLocal的set方法,实际上是在main主线程的环境中完成的,因此也只能在main主线程中get到,而run方法运行的上下文是子线程本身,由于run方法中并没有使用set方法赋值,因此get到的是默认空值null.
ThreadLocal还有一个派生的子类:InheritableThreadLocal ,可以允许线程及该线程创建的子线程均可以访问同一个变量(有些OOP中的proteced的意味),这么解释可能理解起来比较费劲,还是直接看代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
main:1
R-A => main:1
R-B => main:1
A:1
B:1
观察下结果,在主线程main中设置了一个InheritableThreadLocal实例,并在main主线程中设置了值1,然后main主线程及二个子线程t1,t2均正常get到了该值。
实现原理:
可以观察下ThreadLocal及Thread的源码,大致了解其实现原理:
ThreadLocal类的get方法
1 2 3 4 5 6 7 8 9 10 |
|
从代码上看,主要思路如下:
1.取当前线程
2.取得ThreadLocalMap类(先不管这个的实现,从命名上看,理解成一个Map<K,V>容器即可)
3.如果Map容器不为空,则根据ThreadLocal自身的HashCode(见后面的继续分析)取得对应的Entry(即Map里的k-v元素对)
4.如果entry不为空,则返回值
5.如果Map容器为空,则设置初始值
继续顺藤摸瓜:
ThreadLocal的getMap及ThreadLocalMap的getEntry方法
1 2 3 |
|
可以发现getMap其实取的是Thread实例t上的一个属性,继续看Thread的代码:
1 2 3 4 5 6 7 8 9 |
|
说明每个Thread内部都维护着二个ThreadLocalMap,一个应对threadLocals(即:一个Thread内部可以有多个ThreadLocal实例),另一个对应着 inheritableThreadLocals,再看ThreadLocal.ThreadLocalMap的getEntry方法
1 2 3 4 5 6 7 8 |
|
从这里看,ThreadLocalMap的key是基于ThreadLocal的Hashcode与内部table的长度-1做位运算的整数值,只要有个印象,threadLocalMap的key跟ThreadLocal实例的hashcode有关即可。
最后看看ThreadLocal的setInitialValue方法:
1 2 3 4 5 6 7 8 9 10 |
|
先根据当前线程实例t,找到内部维护的ThreadLocalMap容器,如果容器为空,则创建Map实例,否则直接把值放进去(Key跟ThreadLocal实例本身的hashCode相关)。
根据以上分析,对于ThreadLocal的内部实现,其主要思路总结如下:
1. 每个Thread实例内部,有二个ThreadLocalMap的K-V容器实例(分别对应threadLocals及inheritableThreadLocals), 容器的元素数量,即为Thread实例里的ThreadLocal实例个数
2. ThreadLocalMap里的每个Entry的Key与ThreadLocal实例的HashCode相关(这样,多个ThreadLocal实例就不会搞混)
3. 每个ThreadLocal实例使用set赋值时,实际上是在ThreadLocalMap容器里,添加(或更新)一条Entry信息
4. 每个ThreadLocal实例使用get取值时,从ThreadLocalMap里根据key取出value 。
作者:繁繁点点
链接:https://www.imooc.com/article/26795?block_id=tuijian_wz
来源:慕课网