一、线程池的由来
调度是有开销的,最开始,调度的基本单位是进程,随着计算机系统的发展,我们发现进程调度的开销太大,并发性也并不高,我们就创造了线程。
线程是为了降低进程的调度开销而出现的,线程比进程更轻量,线程是被包括在进程里面的,一个进程可以有多个线程,这多个线程共享进程的内存空间等资源。线程被创建出来之后,线程就变成了调度的基本单位。
随着时代的发展,我们认为线程的并发性也不够高了,此时出现了两种解决办法,协程和线程池。
本文主要介绍线程池。介绍线程池首先要了解一个背景知识
二、内核态和用户态
在计算机系统中,程序运行被分为两种状态,内核态和用户态,内核态和用户态的主要是指cpu运行的两种状态,内核态的权限高,可以访问任何硬件数据,用户态只能访问被授权的文件数据。一般来说,内核态里都是操作系统的代码运行,用户态是用户的软件运行
进程和线程的创建就是操作系统在内核态进行的,操作系统提供了一些api让软件程序调用,使得软件也能创建线程,此时创建的线程和进程是操作系统管理的,操作系统对这些线程进行维护。
但是操作系统也还有其他的工作要做,操作系统面向了所有此时正在运行的软件程序并且操作系统需要维护一些操作系统的底层逻辑,当多个程序都调用api时,每个程序的调用什么时候执行是由操作系统决定的,对用户程序来说,这些调用是不可控的,对操作系统来说,这些调用也会增加操作系统的负担,所以一般认为,如果一个系统内核执行的任务被安排在用户态执行,这项任务就变得高效起来。所以基于用户态的线程池就应运而生;用户态的线程池它的里面的线程的创建销毁调度都由用户态程序处理,不再通过操作系统,这不仅减少了用户态和内核态的切换,也让操作系统减轻了负担。这就是线程池高效的原因。
三、如何创建线程池
线程池在java中被封装成了一个类可以直接使用,ExecutorService类和线程池创建的工厂类Executors。
//创建一个有十个线程的线程池
ExecutorService pool=Executors.newFixedThreadPool(10);
上面的代码创建一个有十个线程的线程池,其中ExecutorService类是线程池类,Executors是创建线程池的工厂类。newFixedThreadPool代表的是创建固定线程数目的线程池。
还有
newCachedThreadPool;创造线程数目动态增长的线程池
newSingleThreadPool;创造只有一个线程的线程池
newScheduledThreadPool:设定延迟时间后执行任务的线程池
四、如何让线程池执行任务
使用submit方法将实现了runnable接口的对象传入线程池,线程池会安排线程执行这些任务
public void static main(String[] args){
//创建一个有十个线程的线程池
ExecutorService pool=Executors.newFixedThreadPool(10);
//向线程池上传任务,线程池会选定执行
for(int i=0;i<1000;i++){
int k=i;
//使用submit将需要执行的任务发送给线程池
pool.submit(new Runnable(){
public void run(){
System.out.println("任务"+k+"执行");
}
});
}
}
五、线程池的简单实现
线程池内部其实使用了消费者模型,内部有一个泛型类型为runnable的泛型阻塞队列,这个队列用来接收外界传入的任务。线程池会创建指定个线程,这些线程就干一件事:一直从这个阻塞队列中拿出runnable对象,拿出来就执行这个对象的run方法,执行完就再拿runnable对象,循环往复。队列中没有对象时,这个线程就会被阻塞队列阻塞,队列中又插入了对象,又会唤醒一个线程来执行,这就是线程池内部的简单逻辑。下面是简单实现代码:
public class MyThreadPool {
BlockingQueue<Runnable> blockingQueue=new ArrayBlockingQueue<>(10);
public void submit(Runnable runnable){
try {
blockingQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public MyThreadPool(int n){
for (int i = 0; i < n; i++) {
Thread t=new Thread(()->{
while(true){
try {
Runnable runnable=blockingQueue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
}
面试问题:线程池应该创建多少个线程?
这个只能在实际编程中根据情况做实验。有两种极端情况。
(1)线程执行的任务全是cpu密集型
如果线程池要做的任务全是cpu密集型的任务,那线程池最多创建和cpu核数相同的线程就可以,因为cpu密集型任务会一直占用cpu,你创建再多线程,没有CPU运行,你也只能等着
(2)线程执行的任务全是io密集型
如果线程执行的任务全是io密集型任务,那次是线程池肯定应该创建更多的线程,因为每个线程都在等待io,cpu就被空闲出来了。但是也不是越多线程就越好,毕竟线程调度和线程创建也有开销。
在实际情况中,往往不存都是io和cpu密集型任务混杂的,此时我们只能做实验检测线程数目创建多少合适。