多进程就是为了解决并发编程的问题,但是进程在重量级了,进程的创建和销毁都在消耗CPU资源了,于是就引入了线程,线程了就成为了并发编程的刚需,线程比进程要轻量很多,并且线程的创建和销毁也比进程要轻量很多,但是在某些场景,线程的创建和销毁要是很频繁,那么线程创建和销毁的开销也是非常大的。
为了解决这样的问题,我们就引出了线程池:
我们已经了解了常量池,字符串连接池这些池子,下来就跟着我的节奏一起去揭开线程池的神秘面纱吧!!!
为什么在线程池中使用拿线程要比直接在系统中创建和销毁线程要轻量呢?
要搞明白这个问题,我们需要了解用户态操作和内核态操作。
我们从线程池中拿线程,就是纯用户态的操作,但是在系统中创建线程,就是用户态和内核态之间的切换,真正的创建内核态操作完成的。
操作系统:内核+配套的应用程序。
内核:操作系统中核心的功能模块集合,比如:硬件管理,驱动管理,文件管理,进程管理,内存管理...... 内核需要给上层的应用程序提供服务。
下来我们看一个具体的栗子来了解下为什么直接在操作系统中创建和申请线程要比在线程池中使用线程线程要重量级。
假如,我们去学校打印店里面去打印一个东西的时候,这时,打印店里面的人比较多,此时打印店老板给你说:同学,你是要在门口的自助打印机上自己打印,还是要等着让我给你打印,如果说我们是要自己在门口的自助打印机上自己打印,那么这个打印东西所消耗的时间完全是在我们可控的范围之内的,如果此时我们要是让老板给我们打印,那么打印完我们的东西需要多少时间就不是我们可以控制的了。要是此时打印东西的人比较多,可以需要的时间就很多,如果此时老板终于打印到你的东西了,正好此时,有一个人给老板打了个电话,此时老板接完电话之后突然找不到了,那么此时是不是很难受。
此时,这个打印操作就映射到了我们用户态和内核态中了。
如果是单纯的用户态操作,就和我们上面打印东西的一样,我们使用自助打印机,时间是可控的,让老板给我们打印东西,时间就不是我们可以控制的了。
因为我们在线程池中操作线程是用户态操作。此时开销就很小,所有在某些场景下,我们使用线程池的效率是非常高的。
1:降低资源的消耗
2:提升响应速度
3:提高线程的可管理性
通过重复利用已经创建好的线程降低线程的创建和销毁造成的消耗。
当某个任务到达时,任务可以不等待线程的创建而直接开始执行。
线程是操作系统的稀缺资源,如果一直无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以把线程进行统一的管理和资源分配以及调度和监控。
ExecutorService pool = Executors.newFixedThreadPool(10); //创建10个线程的线程池
Executors中的静态方法newFixedThreadPool创建了10个线程的线程池。
返回值类型为ExecutorService
通过返回的对象的中的submit方法就可以注册一个任务到线程池中。
//添加任务到线程池中
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
通过Executors类创建线程池的几种方式:
public static void main(String[] args) {
//根据需求量创建线程,可灵活的收回空闲线程
ExecutorService pool1 = Executors.newCachedThreadPool();
//线程池中只有一个线程
ExecutorService pool2 = Executors.newSingleThreadExecutor();
//线程池中有10个线程
ExecutorService pool3 = Executors.newFixedThreadPool(10);
//设置延时时间后执行。
ExecutorService pool4 = Executors.newScheduledThreadPool(1000);
}
class MyThreadPool {
//阻塞队列用于存放任务
private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();
//把任务添加到阻塞队列中去
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
//此处实现一个固定数量的线程池
public MyThreadPool (int n) {
for (int i = 0; i < n; i++) { //连续创建N个线程
Thread t = new Thread(() ->{
try {
while (true) {
Runnable runnable = queue.take(); //把阻塞队列中的任务取出来
runnable.run(); //执行任务
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//启动
t.start();
}
}
}
public class threadDemo {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool(10); //给线程池中创建10个线程
for (int i = 0; i < 1000; i++) {
int num = i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello "+ num);
}
});
}
}
}
上述的ExectuorService和newFixedThreadPool都是对ThreadPoolExecutor进一步封装。
ThreadPoolExecutor是原装的线程池对象。
下面我们来看看ThreadPoolExecutor类的构造:
corePoolSize 核心线程数
maximumPoolSize 最大线程数
如果当前任务较多,线程池就会创建一些临时线程,如果当前空闲了,线程池就会把多出来的临时线程销毁
KeepAliveTime 保持存活时间
当任务少的时候,整体空闲的时候,临时线程不会被立即销毁,而是保持一定的时间,再去销毁。描述了临时线程最大存活时间。
unit 单位,存活时间的单位(毫秒,秒,分钟....)
BlockingQueue<Runnable> workQueue 线程池中有很多任务,也是通过阻塞队列来组织管理的。
我们可以手动给线程一个阻塞队列,此时就很方便的控制/获取队列中的信息了。submit方法就是把该任务放到队列中。
ThreadFactory threadfactory 线程工厂,用来创建线程。
RejectedExecutionHandler handler 线程池的拒绝策略,就是线程池中的任务已经满了,我们如何进行拒绝。
标准库提供了4中拒绝策略: