写在前面的话:本文给出了如何创建一个有界线程池的一种方法,并对其中的问题进行了分析理解,其中最后一个分析问题个人觉得非常有价值,通过这个问题能帮我们更好的理解线程池。
1.创建无界线程池可能会造成的问题
在上一篇博客中我们对线程池有一个简单的了解,并知道了如何创建线程池以及让线程池中的线程执行。但是上一篇博客中所用的newCachedThreadPool()方法创建线程并不好,因为如果使用不当会造成内存溢出异常。参见博客:https://blog.csdn.net/wenniuwuren/article/details/51700080
2.如何创建指定线程数量的线程池?
代码如下:
package com.springboot.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestMyFixedThreadPool {
public static void main(String[] args) {
/*创建指定数量的线程池*/
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyRunnable("小明"));
executorService.execute(new MyRunnable("小华"));
executorService.execute(new MyRunnable("李雷"));
executorService.execute(new MyRunnable("韩梅梅"));
executorService.execute(new MyRunnable("小王"));
}
}
class MyRunnable implements Runnable{
private String userName;
MyRunnable(String userName){
this.userName=userName;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
Thread.sleep(300l);
System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
pool-1-thread-2 userName=小华 begin 1572234540656
pool-1-thread-1 userName=小明 begin 1572234540656
pool-1-thread-3 userName=李雷 begin 1572234540681
pool-1-thread-2 userName=小华 end 1572234540956
pool-1-thread-1 userName=小明 end 1572234540957
pool-1-thread-1 userName=韩梅梅 begin 1572234540958
pool-1-thread-2 userName=小王 begin 1572234540958
pool-1-thread-3 userName=李雷 end 1572234540981
pool-1-thread-2 userName=小王 end 1572234541258
pool-1-thread-1 userName=韩梅梅 end 1572234541258
从运行结果可以看出,由于我们一开始的时候指定线程池大小是3,但是加入了5个任务,所以开始时候只能有3个任务在运行,当线程池中的线程有空闲时立马接受新的任务并开始执行,从运行结果上面我们也可以看出他们在时间是连续的:即上一个任务的结束时间即为下一个任务的开始时间。
3.引入问题分析:当线程池中的线程把任务执行完以后它们还在继续运行吗?
如果我们没有试验或者没有深入了解线程池的话,我们可能会这样回答,都执行完成任务了还执行个啥啊?为了节省资源肯定是不运行。(语气肯定,信心满满)但是实际上却是这样的:
如图,我们可以看到此时任务全部执行完了,但是程序依然在运行,说明仍然有线程在运行。说明我们的臆想出来的答案是错误的。所以新的思考随之 而来:到底是什么线程在运行呢?我的猜想是这样:当我线程开始执行以后会随之创建一个新的守护线程,除非认为关闭否则他将一直在运行,从而导致程序一直在运行,至于线程池中的线程我也认为是任务结束以后将会死亡。但是只有猜想没有验证是肯定行不通的,上一个猜想才让我被打脸。
验证猜想:使用java虚拟机提供的管理工具来进行分析,打开管理工具(打开命令行窗口输入:jvisualvm命令)
红色标注的地方为当前正在运行的程序,点开以后我们可以看到此程序所包含的线程。
我们惊奇的发现,我创建线程池的时候指定的三个线程它们依然还在,状态为“驻留”状态。那为什么是这样的呢?
看源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
此方法的第一个和第三个参数分别为:corePoolSize、keepAliveTime
继续进入ThreadPoolExecutor类:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
看到这两参数的注释:
corePoolSize:the number of threads to keep in the pool, even if they are idle.【除非设置了{@code allowCoreThreadTimeOut},即使它们处于空闲状态也要保留在池中的线程数】
keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.【当线程数大于内核数时,这是多余的空闲线程将在终止之前等待新任务的最长时间。】
我们可以反过来想:如果线程的数量没有超过内核数,就算是空闲线程他也将一直处于运行状态。
此时又有一个新问题,是不是当我们创建一个指定数量的线程池的时候,就已经有指定数量的在线程池中运行呢?
通过代码来验证:
package com.springboot.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestMyFixedThreadPool {
public static void main(String[] args) {
/*创建指定数量的线程池*/
ExecutorService executorService = Executors.newFixedThreadPool(3);
}
}
class MyRunnable implements Runnable{
private String userName;
MyRunnable(String userName){
this.userName=userName;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
Thread.sleep(300l);
System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
再看java虚拟机:
之前的程序不在了。
执行一个任务:
package com.springboot.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestMyFixedThreadPool {
public static void main(String[] args) {
/*创建指定数量的线程池*/
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyRunnable("小明"));
/* executorService.execute(new MyRunnable("小华"));
executorService.execute(new MyRunnable("李雷"));
executorService.execute(new MyRunnable("韩梅梅"));
executorService.execute(new MyRunnable("小王"));*/
}
}
class MyRunnable implements Runnable{
private String userName;
MyRunnable(String userName){
this.userName=userName;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
Thread.sleep(300l);
System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
任务执行完,程序依然在运行。
我们可以看到,此时有一个线程在运行,导致程序在运行。
至此我们可以得出结论:
当我们创建一个指定数量的线程池的时候,会初始化一个线程指定大小线程池,但是并没有线程在运行,只有当我们调用executorService.execute();方法以后才会有线程加入线程池,并且当他执行完任务以后(处于空闲状态)它不会被移除线程池(前提:线程数没有超过核心线程池大小,如果线程数超过内核数的话他将会在超过keepAliveTime以后消失),而是一直处于驻留状态,同时实际线程的数量遵行这样的原则:如果线程数小于线程池的大小以实际线程数量为准,最大只能和线程池大小一样。【注:本文精华】
参考文献:《java并发编程核心框架与方法》