虽然线程的创建和销毁的开销比较小, 但还是有的, 如果频繁的创建和销毁线程, 开销还是比较大的.解决: 线程池或者协程, 本文主讲线程池.
线程池: 把线程提前创建好, 放到池子里, 后面需要用到线程直接从池子里面取, 不必从系统申请,
线程用完, 不是还给系统, 而是放到池子里面, 必备下次使用, 这样就减去了频繁创建销毁线程的开销.
举个栗子:
这个就是我们要带出的线程池的模式。
线程池最大的好处就是减少每次启动、销毁线程的损耗。
为什么线程放到池子里面就比从系统申请释放更快 ?
这就涉及到操作系统的用户态与内核态:
自己写的代码运行在用户态, 有些代码需要通过调用操作系统的 API, 进一步的逻辑会在内核中执行.
比如 System.out.println, 本质上需要经过 write 系统调用进入到内核中, 在内核里面会执行一些逻辑, 在内核中运行的代码称为 “内核态” 运行的代码.
创建线程本身需要内核的支持, 创建线程本身就是在操作系统内核中创建 PCB, 调用 Thread.start 也要进入内核态运行.
而把创建好的线程放到 “池子里”, 由于 “池子” 是用户态实现的, 放到池子里/从池子里取这个过程不需要涉及到内核态, 用纯粹的用户代码就能完成, 一般认为纯用户态的操作效率比经过内核态处理的操作效率更高.
为什么 ?
不是说内核处理的效率一定真的低, 而是说代码进入内核态之后运行就不可控了, 你不知道此时内核背负了多少任务, 内核什么时候执行我们的任务, 什么时候运行完就不知道了, 有时快有时慢, 所以是不可控的.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
Executors 创建线程池的几种方式:
所以加上 ThreadPoolExecutor 共有 7 种创建线程池的方法
class MyThreadPool {
//任务直接用Runnable,不用另外创建类了
//用阻塞队列来组织任务
private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();
//用来描述线程的类
static class Worker extends Thread{
private BlockingQueue<Runnable> queue=null;
//利用构造方法获取任务队列
public Worker(BlockingQueue<Runnable> queue){
this.queue=queue;
}
@Override
public void run() {
while(true){
try {
//如果队列为空则阻塞
Runnable runnable=queue.take();
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//用数组来当作池存储线程
List<Thread> list=new ArrayList<>();
//构造方法中创建若干线程放到线程池中
public MyThreadPool(int n){
for(int i=0;i<n;i++){
Worker worker=new Worker(this.queue);
//注意不要忘记start
worker.start();
//加入线程池中
list.add(worker);
}
}
//提交任务
public void submit(Runnable runnable) {
try {
this.queue.put(runnable);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
MyThreadPool pool=new MyThreadPool(10);
for(int i = 1;i <= 100;i++){
int n = i;
//submit了100次,相当于100个任务进入了任务队列,每个线程分一些,很快就执行完了
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("myThreadPool: " + n);
}
});
}
}
}
有一个程序, 这个程序需要并发的完成一些任务, 如果使用线程池的话, 线程池中的线程数设置为多少合适 ?
回答:
为什么不让 CPU 占有率 过高 ?
对于线上服务器, 一定要留有一定的冗余以便随时应对可能的突发情况, 例如请求暴涨, 若本身就把 CPU 快占完了, 这是突然来了一波请求高峰,此时服务器可能直接就挂了.
总结: 自己实现线程池