博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线程池的相关概念以及作用
阅读量:3957 次
发布时间:2019-05-24

本文共 5746 字,大约阅读时间需要 19 分钟。

线程池的相关概念以及作用:

1. 线程池的概念:

​ 线程池就是创建一些线程,它们的集合称之为线程池。

​ 使用线程池可以很好地提高系统的性能,线程池在系统启动时,即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一个线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中,成为空闲状态,等待执行下一个任务。

2. 线程池的工作机制

​ 1)在线程池的编程模式下,系统是将任务传给整个线程池,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给这个空闲的线程。

​ 2)一个线程同时只能执行一个任务,但是可以同时向一个线程池提交多个任务。

3. 使用线程池的好处

第一:降低资源消耗。通过重复利用已创建的线程,来避免了线程的创建和销毁所造成的资源消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建,就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以对线程们进行统一分配、调优和监控,提高了对线程的可管理性。

4. 四种线程池:

Executors:在java.util.concurrent包下面的这个Executors类中,提供了一系列的静态工厂方法,用于创建各种线程池;

其中常用的几个方法,如下:

public static ExecutorService newFixedThreadPool()public static ExecutorService newSingleThreadExecutor()public static ExecutorService newCachedThreadPool()public static ScheduledExecutorService newSingleThreadScheduledExecutor()public static ScheduledExecutorService newScheduledThreadPool()

   1、newFixedThreadPool:固定数量的线程池,该方法返回一个 可重用的、`固定线程数量`的线程池;  2、newSingleThreadExecutor:单线程的线程池,它只会用`唯一的线程`来执行任务,保证所有任务按照`指定顺序(FIFO(先进先出),  LIFO(后进先出),  优先级)`执行;  3、newCachedThreadPool:`可缓存线程池`,该线程池可以根据实际情况`调整池子中的线程数量`,当执行当前任务时,上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程,如果上一个线程没有结束才会新建线程,可缓存型池子通常用于执行一些生存期较短的任务;  4、newScheduledThreadPool:`可定时线程池`,该线程池可以设定线程的执行时间,可以用来去执行一些`定时及周期性`的任务。

5. 线程池是如何创建的:

​ “引: JDK:(Java development toolkit),Java开发工具集 ;”

JDK提供了Executor接口(隶属于java.util.concurrent包),可以让我们有效的管理和控制我们的线程。

看源码:

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
()));}

​ 根据源码可以看出,在实际创建线程池的时候,实质调用的是ThreadPoolExecutor这个类,即,线程池执行器ThreadPoolExecutor

​ 即,在Executors类的内部,创建线程池的时候,实际新建的是一个ThreadPoolExecutor对象。

总结:说白了,线程池是通过new 一个 ThreadPoolExecutor()来创建的;

6. 线程池中是如何实现线程复用的?

1)引言,线程有两种实现方式,

​ 一种是继承Thread,重写run方法;

​ 一种是自己写一个Task实现runable接口,然后重写run方法;

启动方式如下:

//第一种方式,继承Thread,重写run方法后的启动new MyThread().start();//第二种方式,将 `实现的runable接口的task作为参数` 传入Thread构造方法new Thread(new Runnable() {
@Override public void run(){
System.out.println("do something"); }}).start();

​ 我们知道,一般一个线程在执行完任务后就结束了,怎么再让他执行下一个任务呢?

​ 要想实现线程复用,必须从Runnable接口的run()方法上入手;

2)看源码:

ThreadPoolExecutor类 :

// 内部类 Workerprivate final class Worker extends AbstractQueuedSynchronizer implements Runnable         {
/** 各种代码... */ Runnable firstTask; Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } public void run() {
runWorker(this); } } //addWorker()方法,启动线程; private boolean addWorker(Runnable firstTask, boolean core) {
w = new Worker(firstTask); final Thread t = w.thread; //...代码 // 很明显了,从这里启动线程的; t.start(); ...//代码 } // getTask()方法,很显然这个方法是从 任务队列(workQueue)中,获取任务; private Runnable getTask() {
// 各种代码... if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
//... } // ... } // 核心部分:runWorker()方法: final void runWorker(Worker w) {
// 各种代码... Runnable task = w.firstTask; w.firstTask = null; // 各种代码... try {
// 重点:看这个大while循环:实现的线程复用; while (task != null || (task = getTask()) != null) {
// 各种代码... } } }

3)源码分析—在线程池中,线程是如何创建的:

​ 我们从第5节知道,线程池是通过 new 一个 ThreadPoolExecutor 这个类 来创建的;

​ 而我们的线程,是在ThreadPoolExecutor类里边的 一个名叫 Worker的内部类 里边 创建的;

​ 也就是说,只要 new Worker(Runnable firstTask),就会创建一个线程,因为Worker的构造方法里边有这么一句:this.thread = getThreadFactory().newThread(this);

​ 并且创建线程的时候,会将这个内部类本身this 传到参数列表里边,去当task。

4)源码分析—在线程池中,线程是如何启动的:

​ 在源码的addWorker()方法中,可以看到:t.start();;

​ 很明显了,从addWorker()方法这里启动线程的;

5)源码分析— 在线程池中,线程是如何运行的:

​ 从源码的addWorker()方法中,我们可以看到:w = new Worker(firstTask);

​ 也就是说,在线程启动的时候我们将内部类worker对象传入进去了,而内部类Worker是实现了runable接口、并重写了run()方法的;

​ 这也意味着:jvm会执行Worker里的run方法,使线程进入运行状态;

6)源码分析— 在线程池中,线程是如何实现复用的:

用Work内部类中的run()方法里边的runWorker()方法里边的while大循环,实现了线程的复用;

​ 在Work内部类中的run()方法里面,有一个runWorker(this)方法;(这个this指的是内部类Work对象本身)

//1,这是Work内部类中的run()方法,线程启动的时候jvm会执行它,public void run() {
runWorker(this);}
final void runWorker(Worker w) {
// 各种代码... // 核心:这个大while循环: while (task != null || (task = getTask()) != null) {
// 各种代码... } // 各种代码...}

线程复用的核心,在这个runWorker()方法里边:

​ 从源码中可以看出,这个runWorker()方法里面,有一个大大的while循环

① 当我们的task任务,不为空的时候,即,通过getTask()方法,可以一直获取到新的任务 ,那么这个while循环就永远在进行;

​ 从而runWorker()方法不会停止,runWorker()方法外边的run()方法也就不会停止,继而线程会一直处于运行状态,去执行新的任务,从而达到了线程复用的目的

② 当我们的task任务为空了**,则while循环结束,**也就是说,这个线程结束 。

7)补充: 剖析getTask()方法:

// getTask()方法,很显然这个方法是从 任务队列(workQueue)中,获取任务; private Runnable getTask() {
// 各种代码... if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
//... } // ... }

​ 根据getTask()这个方法的源码可以看出,getTask()方法是从任务队列中(workQueue),获取任务;

​ 这个getTask()方法里面有个三元表达式,

​ ① 当条件为真时,从任务队列(workQueue)里面,取得要执行的任务;

​ ② 当条件为假时,即任务队列没有任务了,则结束runWorker()方法里边的while大循环,从而,这个线程结束。

8) 线程复用的流程,总结

​ ①,创建一个线程池,new ThreadPoolExecutor()

​ ②,新建一个线程,在线程池中,new Worker()新建一个Worker内部类时,会新建一个线程getThreadFactory().newThread(this),并且会把这个Worker内部类本身传进去当作任务去执行,

​ ③,这个worker内部类的run()方法里的runWorker()方法,写了一个while大循环,

​ ④,当任务队列有任务时,while大循环一直进行,从而runWorker()、run()方法也就一直进行,继而该线程一直执行新的任务,达到了线程复用的目的;

​ ⑤,当任务队列没有任务时,则结束这个while循环,继而,这个线程也就结束。

转载地址:http://lktzi.baihongyu.com/

你可能感兴趣的文章
提问的智慧
查看>>
关于dom4j无法解析xmlns问题及生成非UTF-8字符集乱码问题的解决
查看>>
很好的一篇文章 如果让我重做一次研究生 王汎森
查看>>
保护U盘批处理文件
查看>>
hibernate 自动导入sql 文件import.sql 国际化编码的问题的解决方案
查看>>
第七颗头骨 & 忘魂花 凤凰
查看>>
李小龙哲学之言
查看>>
潜伏中体现的潜规则
查看>>
[Java] Appfuse 源代码分析
查看>>
[Java] Appfuse 最佳实践
查看>>
[心情] 如果有一天
查看>>
[随笔] 6月近况小记 & 一个站点优化问题
查看>>
[Perl] 关于 Bugzilla 的一些问题与研究
查看>>
[Linux] 常用 linux 系统命令及维护备忘
查看>>
[Linux] 关于 Ext4 HowTo
查看>>
[杂记] 新年物语&关于Mysql引擎性能测试
查看>>
[心得] 近期更新&关于Infobright
查看>>
[杂记] 流量统计 & 短信接口
查看>>
[Java] JRebel + Maven + Jetty 热部署
查看>>
[算法] 从 Memcached 分布式应用看一致性哈希散列函数的选择
查看>>