我是靠谱客的博主 坦率宝马,这篇文章主要介绍多线程之线程池详解,现在分享给大家,希望可以做个参考。

1、线程池家族

线程池的最上层接口是Executor,这个接口定义了一个核心方法execute(Runnabel command),这个方法最后被ThreadPoolExecutor类实现,这个方法是用来传入任务的。而且ThreadPoolExecutor是线程池的核心类,此类的构造方法如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

参数解析:
corePoolSize:核心线程池的大小,如果核心线程池有空闲位置,这是新的任务就会被核心线程池新建一个线程执行,执行完毕后不会销毁线程,线程会进入缓存队列等待再次被运行。

maximunPoolSize:线程池能创建最大的线程数量。如果核心线程池和缓存队列都已经满了,新的任务进来就会创建新的线程来执行。但是数量不能超过maximunPoolSize,否侧会采取拒绝接受任务策略,我们下面会具体分析。

keepAliveTime:非核心线程能够空闲的最长时间,超过时间,线程终止。这个参数默认只有在线程数量超过核心线程池大小时才会起作用。只要线程数量不超过核心线程大小,就不会起作用。

unit:时间单位,和keepAliveTime配合使用。

workQueue:缓存队列,用来存放等待被执行的任务。

threadFactory:线程工厂,用来创建线程,一般有三种选择策略。

复制代码
1
2
3
4
ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue;

handler:拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略,四种策略为

复制代码
1
2
3
4
5
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理(就是提交该任务的线程)该任务

2、线程池实现原理

在这里插入图片描述
线程池的状态:
线程池和线程一样拥有自己的状态,在ThreadPoolExecutor类中定义了一个volatile变量runState来表示线程池的状态,线程池有四种状态,分别为RUNNING、SHUTDOWN、STOP、TERMINATED。

线程池创建后处于RUNNING状态。

调用shutdown后处于SHUTDOWN状态,线程池不能接受新的任务,会等待缓冲队列的任务完成。

调用shutdownNow后处于STOP状态,线程池不能接受新的任务,并尝试终止正在执行的任务。

当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

总结:

  • 如果当前线程池中的线程数目小于corePoolSize核心线程数,则每来一个任务,就会创建一个核心线程去执行这个任务。如果核心线程数还有剩,是不会将任务放到队列的。

  • 如果当前线程池中的线程数目大于或等于corePoolSize核心线程数,则每来一个任务,会尝试将其添加到任务缓存队列中,若添加成功,则该任务就会等待空闲线程将其取出去执行;若添加失败,即任务缓存队列已满,则会尝试创建新的非核心线程去执行这个任务,这个非核心线程是由时间限制的。

  • 如果当前线程池中的线程数达到maximumPoolSize最大线程数,一般任务缓存队列也是满的,才可能达到最大线程数,则会采取任务拒绝策略进行处理。

  • 如果线程池中的线程数量大于 corePoolSize时,则非核心线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

3、线程池使用

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.yqg.java; public class Task implements Runnable{ private int num; public Task(int num) { this.num=num; } @Override public void run() { System.out.println("正在执行任务 "+num); try { //设置睡眠时间,模拟执行任务 Thread.currentThread().sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程"+num+"执行完毕"); } }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.yqg.java; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Test4 { public static void main(String[] args) { //5个核心线程,最大线程数为10,空闲时间为200,任务缓存队列为5 ThreadPoolExecutor pool=new ThreadPoolExecutor(5,10,200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5)); for(int i=0;i<15;i++) { Task task=new Task(i); pool.execute(task); System.out.println("线程池中线程数目:"+pool.getPoolSize()+",队列中等待执行的任务数目:"+ pool.getQueue().size()+",已执行玩别的任务数目:"+pool.getCompletedTaskCount()); } pool.shutdown(); } }

一种可能的情况:

复制代码
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
正在执行任务 0 线程池中线程数目:1,队列中等待执行的任务数目:0,已执行玩别的任务数目:0 线程池中线程数目:2,队列中等待执行的任务数目:0,已执行玩别的任务数目:0 线程池中线程数目:3,队列中等待执行的任务数目:0,已执行玩别的任务数目:0 正在执行任务 1 正在执行任务 2 线程池中线程数目:4,队列中等待执行的任务数目:0,已执行玩别的任务数目:0 正在执行任务 3 线程池中线程数目:5,队列中等待执行的任务数目:0,已执行玩别的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:1,已执行玩别的任务数目:0 正在执行任务 4 线程池中线程数目:5,队列中等待执行的任务数目:2,已执行玩别的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:3,已执行玩别的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:4,已执行玩别的任务数目:0 线程池中线程数目:5,队列中等待执行的任务数目:5,已执行玩别的任务数目:0 线程池中线程数目:6,队列中等待执行的任务数目:5,已执行玩别的任务数目:0 正在执行任务 10 线程池中线程数目:7,队列中等待执行的任务数目:5,已执行玩别的任务数目:0 正在执行任务 11 线程池中线程数目:8,队列中等待执行的任务数目:5,已执行玩别的任务数目:0 正在执行任务 12 线程池中线程数目:9,队列中等待执行的任务数目:5,已执行玩别的任务数目:0 正在执行任务 13 线程池中线程数目:10,队列中等待执行的任务数目:5,已执行玩别的任务数目:0 正在执行任务 14 线程2执行完毕 线程1执行完毕 线程4执行完毕 线程3执行完毕 正在执行任务 8 线程0执行完毕 正在执行任务 9 正在执行任务 7 正在执行任务 6 正在执行任务 5 线程12执行完毕 线程13执行完毕 线程11执行完毕 线程10执行完毕 线程14执行完毕 线程7执行完毕 线程9执行完毕 线程8执行完毕 线程5执行完毕 线程6执行完毕

从结果可看,当线程池的数目超过5时,便将任务放入任务缓存队列中,最多也只能放5个,便创建非核心线程,非核心新城最多也只能有5个,超出,则要抛出任务拒绝异常了。

一般我们不直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池 public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }

从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

最后

以上就是坦率宝马最近收集整理的关于多线程之线程池详解的全部内容,更多相关多线程之线程池详解内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(84)

评论列表共有 0 条评论

立即
投稿
返回
顶部