本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net
介绍
为什么要有线程池这个东西?
创建线程对象不像其他对象一样在 JVM 分配内存即可,还要调用操作系统内核的 API,然后操作系统为线程分配一系列的资源,这个成本就很高了。所以线程是一个重量级对象,应该避免频繁创建和销毁
再说一下线程池的大概工作流程
以前我们运行线程的时候 new Thread().start() 即可,如果线程数多了,频繁的创建线程和销毁线程很费时间。
然后 Doug Lea 就这样设计了一下,预先启动几个线程,还准备好一个容器。每次想执行任务时,就将实现了 Runnable 接口的任务放到这个容器中,预先启动好的线程不断从容器中拿出任务,调用执行 Runnable 接口的 run 方法,这样刚开始启动的线程就能执行很多次任务。大概流程就是这样,真正的线程池考虑的东西比较多。
想到没有,这就是典型的生产者 - 消费者模式,线程池的使用者是生产者,线程池本身是消费者。用代码来实现一下
手写线程池
省略 try catch 版
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
public class MyThreadPool {
/** 利用阻塞队列实现生产者-消费者模式 */
BlockingQueue<Runnable> workQueue;
/** 保存内部工作线程 */
List<WorkThread> workThreadList = new ArrayList<>();
MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
for (int i = 0; i < poolSize; i++) {
WorkThread workThread = new WorkThread();
workThread.start();
workThreadList.add(workThread);
}
}
void execute(Runnable command) {
// 放入任务,如果没有空间,则阻塞等待
// try catch部分省略
workQueue.put(command);
}
class WorkThread extends Thread {
@Override
public void run() {
// 循环取任务并执行
while (true) {
Runnable task = null;
// 获取阻塞队列的第一个任务,并删除
// 如果没有元素,则会阻塞等待
// try catch部分省略
task = workQueue.take();
task.run();
}
}
}
public static void main(String[] args) {
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
MyThreadPool pool = new MyThreadPool(2, workQueue);
for (int i = 0; i < 10; i++) {
int num = i;
pool.execute(()->{
System.out.println("线程 " + num + " 执行");
});
}
}
}
|
可以正常工作
不省略 try catch 版本
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
public class MyThreadPool {
/** 利用阻塞队列实现生产者-消费者模式 */
BlockingQueue<Runnable> workQueue;
/** 保存内部工作线程 */
List<WorkThread> workThreadList = new ArrayList<>();
MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
for (int i = 0; i < poolSize; i++) {
WorkThread workThread = new WorkThread();
workThread.start();
workThreadList.add(workThread);
}
}
void execute(Runnable command) {
// 放入任务,如果没有空间,则阻塞等待
try {
workQueue.put(command);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class WorkThread extends Thread {
@Override
public void run() {
// 循环取任务并执行
while (true) {
Runnable task = null;
try {
// 获取阻塞队列的第一个任务,并删除
// 如果没有元素,则会阻塞等待
task = workQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
task.run();
}
}
}
public static void main(String[] args) {
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
MyThreadPool pool = new MyThreadPool(2, workQueue);
for (int i = 0; i < 10; i++) {
int num = i;
pool.execute(()->{
System.out.println("线程 " + num + " 执行");
});
}
}
}
|
来看一下 Java 中的线程池类 ThreadPoolExecutor 的构造函数有哪些参数?
1
2
3
4
5
6
7
8
|
public ThreadPoolExecutor
(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
|
来类比学习一下这些参数,我们把线程池类比为项目组,线程是这个项目组的成员
corePoolSize:线程池中最少的线程数,一个项目组总得有 corePoolSize 人坚守阵地。
maximumPoolSize:当项目很忙时,就得加人,请其他项目组的人来帮忙。但是公司空间有限,最多只能加到 maximumPoolSize 个人。当项目闲了,就得撤人了,最多能撤到 corePoolSize 个人
keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?如果一个线程在 keepAliveTime(时间数字)* unit(时间单位)时间内都没有执行任务,说明这个线程很闲。如果此时线程数大于 corePoolSize,这个线程就要被回收了
workQueue:就是任务队列
threadFactory:自定义如果创建线程,例如给线程指定一个有意义的名字
handler:workQueue 满了(排期满了),再提交任务,该怎么处理呢?这个就是处理策略,线程池提供了 4 种策略,你也可以实现 RejectedExecutionHandler 接口来自定义策略
实现类 |
策略 |
CallerRunsPolicy |
提交任务的线程自己去执行该任务 |
AbortPolicy |
默认的拒绝策略,会 throws RejectedExecutionException |
DiscardPolicy |
直接丢弃任务,没有任何异常抛出 |
DiscardOldestPolicy |
丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列 |
欢迎关注
参考博客