0、引言
在面试中,线程池是其中的一大考点。本篇博客则记录关于线程池的一些总结,包括线程池的使用原因、创建线程池、线程池的形式及线程池的工作流程,希望能对你更好的理解线程有帮助。
1、为什么要创建线程池?
线程池是一种池化技术,目的是避免线程频繁的创建和销毁带来的性能消耗。它是把已创建的线程放入“池”中,当有任务来临时就可以重用已有的线程,无需等待创建的过程,这样就可以有效提高程序的响应速度。
《Java 并发编程艺术》一书中提到使用线程池的好处:
1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的资源浪费。 提高响应速度。当任务到达时,不需要等到线程创建就能立即执行。
2、方便管理线程:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以对线程进行统一的分配,优化及监控。
2、如何创建线程池?
Java 中创建线程池有以下两种方式:
1、通过 ThreadPoolExecutor 类创建(推荐)
2、通过 Executors 类创建
2.1 Executors 类创建线程池
源码:
通过查看源码,我们发现:
1、这两种方式在本质上是一种方式,都是通过 ThreadPoolExecutor 类的方式创建,因为 Exexutors 类调用了 ThreadPoolExecutor 类的方法。
2、其中,上面的三张图片代表着Executors 类创建线程池的三种常用形式:固定线程池、独立线程池和缓冲线程池
实例:
public static void main(String[] args) {
/**
* 创建固定线程池(大小固定)
**/
ExecutorService pool1 = Executors.newFixedThreadPool(2);
Runnable task1 = () -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
// pool1.execute(task1);
// pool1.execute(task1);
// pool1.execute(task1);
/**
* 创建缓冲线程池(大小可变)
**/
ExecutorService pool2 = Executors.newCachedThreadPool();
Runnable task2 = () -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
// pool2.execute(task2);
// pool2.execute(task2);
// pool2.execute(task2);
/**
* 创建独立线程池(线程相互独立)
**/
ExecutorService pool3 = Executors.newSingleThreadExecutor();
Runnable task3 = () -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
pool3.execute(task3);
pool3.execute(task3);
pool3.execute(task3);
}
2.2 ThreadPoolExecutor 类创建线程池
源码:
实例:
public class Thread {
/**
* corePoolSize:线程池中所保存的核心线程数,包括空闲线程
* maximumPoolSize:池中允许的最大线程数。
* keepAliveTime:线程池中的空闲线程所能持续的最长时间,
* 当线程池中的线程数量小于 corePoolSize 时,
* 如果里面有线程的空闲时间超过了 keepAliveTime,
* 就将其移除线程池,这样,可以动态地调整线程池中线程的数量。
* unit:持续时间的单位。
* workQueue:任务执行前保存任务的队列,仅保存由 execute 方法提交的 Runnable 任务。
**/
public static void main(String[] args) {
//创建ThreadPoolExecutor线程池对象,并初始化该对象的各种参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 2000, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(5));
//往线程池中循环提交线程
for (int i = 0; i < 15; i++) {
//创建线程类对象
MyTask myTask = new MyTask(i);
//开启线程
executor.execute(myTask);
//获取线程池中线程的相应参数
System.out.println("线程池中线程数目:" +executor.getPoolSize() + ",队列中等待执行的任务数目:"+executor.getQueue().size() + ",已执行完的任务数目:"+executor.getCompletedTaskCount());
}
//待线程池以及缓存队列中所有的线程任务完成后关闭线程池。
executor.shutdown();
}
}
class MyTask implements Runnable {
private int num;
public MyTask(int num) {
this.num = num;
}
@Override
public void run() {
System.out.println("正在执行task " + num );
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task " + num + "执行完毕");
}
/**
* 获取(未来时间戳-当前时间戳)的差值,
* 也即是:(每个线程的睡醒时间戳-每个线程的入睡时间戳)
* 作用:用于实现多线程高并发
* @return
* @throws ParseException
*/
public long getDelta() throws ParseException {
//获取当前时间戳
long t1 = System.currentTimeMillis();
//获取未来某个时间戳(自定义,可写入配置文件)
String str = "2016-11-11 15:15:15";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long t2 = simpleDateFormat.parse(str).getTime();
return t2 - t1;
}
2.3 为什么不推荐Executors 类创建线程池?
在阿里巴巴java开发手册中明确规定不允许使用Executors创建线程池:
这是什么意思呢?以固定线程池为例:
java中的阻塞队列有两种:
1、ArrayBlockingQueue是一个以数组设计的有界队列,必须设置大小
2、LinkedBlockingQueue 是一个以链表实现的有界阻塞队列,容量可以选择设置,不设置的话是无界的最大长度为 Integer.MAX_VALUE。
可以看到实现是实例化一个ThreadPoolExecutor,创建的队列为 LinkedBlockQueue。
1、由于Executors创建线程池没有传入阻塞队列的长度,阻塞队列就是一个无边界队列。
2、对于一个无边界队列来说是可以向其中无限添加任务的,这种情况下可能由于任务数太多而导致内存溢出。
3、线程池有哪些形式
在了解线程池之前,先来了解一下相关参数:
1、corePoolSize:线程池中所保存的核心线程数,包括空闲线程
2、maximumPoolSize:池中允许的最大线程数。
3、keepAliveTime:线程池中的空闲线程所能持续的最长时间,当线程池中的线程数量小于 corePoolSize 时,如果里面有线程的空闲时间超过了 keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。
4、unit:持续时间的单位。
5、workQueue:任务执行前保存任务的队列,仅保存由 execute 方法提交的 Runnable 任务。
线程池的种类:
01、CachedThreadPool(可缓存的线程池)
特点:
1、该线程池的核心线程数量是0,线程的数量最高可以达到Integer 类型最大值;
2、创建ThreadPoolExecutor实例时传过去的参数是一个SynchronousQueue实例,说明在创建任务时,若存在空闲线程就复用它,没有的话再新建线程。
3、线程处于闲置状态超过60s的话,就会被销毁。
实例:
public static void main(String[] args) throws InterruptedException {
ExecutorService cachedThreadPool=new ThreadPoolExecutor(0,Integer.MAX_VALUE,60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
int index=0;
for (int i = 0; i < 10; i++) {
++index;
Thread.sleep(index*1000);
int finalIndex = index;
cachedThreadPool.execute(() -> {
System.out.println(Thread.currentThread()+":该线程执行了"+ finalIndex*1000+"ms");
});
}
cachedThreadPool.shutdown();
}
02、FixedThreadPool(定长线程池)
特点:
1、线程池的最大线程数等于核心线程数,并且线程池的线程不会因为闲置超时被销毁。
2、使用的列队是LinkedBlockingQueue,表示如果当前线程数小于核心线程数,那么即使有空闲线程也不会复用线程去执行任务,而是创建新的线程去执行任务。如果当前执行任务数量大于核心线程数,此时再提交任务就在队列中等待,直到有可用线程。
实例:
public static void main(String[] args) throws InterruptedException {
ExecutorService fixedThreadPool =new ThreadPoolExecutor(3,3,0, TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(2));
int index=0;
for (int i = 0; i < 10; i++) {
++index;
Thread.sleep(index*1000);
int finalIndex = index;
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread()+":该线程执行了"+ finalIndex*1000+"ms");
});
}
fixedThreadPool.shutdown();
}
03、SingleThreadExecutor(单线程线程池)
特点
该线程池本质上就是只有一个线程数的newFixedThreadPool,它只有一个线程在工作,所有任务按照指定顺序执行。就不贴代码了
04、ScheduledThreadPool(支持定时的定长线程池)
特点:
newScheduledThreadPool的方法不是直接返回一个ThreadPoolExecutor实例,而是通过有定时功能的ThreadPoolExecutor,也就是ScheduledThreadPoolExecutor 来返回ThreadPoolExecutor实例,从源码中可以看出:
1、该线程池可以设置核心线程数量,最大线程数与newCachedThreadPool一样,都是Integer.MAX_VALUE。
2、该线程池采用的队列是DelayedWorkQueue,具有延迟和定时的作用。
实例:
public static void main(String[] args) {
ExecutorService scheduledThreadPool=new ScheduledThreadPoolExecutor(3);
((ScheduledThreadPoolExecutor) scheduledThreadPool).schedule(() -> System.out.println("=====>延迟3秒"),3,TimeUnit.SECONDS);
((ScheduledThreadPoolExecutor) scheduledThreadPool).scheduleAtFixedRate(() -> System.out.println("=====>执行中"),1,2,TimeUnit.SECONDS);
}
4、线程池的工作流程是怎样的?
1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2、当执行 execute() 方法添加一个任务时,线程池会判断:
(a) 若正在运行的线程数量小于 corePoolSize 值,则立刻创建线程运行此任务;
(b) 若正在运行的线程数量大于或等于 corePoolSize 值,则将此任务放入队列;
© 若此时队列满了,而且正在运行的线程数量小于 maximumPoolSize 值,则创建非核心线程立刻运行这个任务;
(d) 若队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize 值,那么线程池会执行设置的拒绝策略。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程空闲时,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize 值,那么这个线程会被销毁。
5、总结
肝文不易,如果对你有帮助,给个赞呗!!!