1.线程池基础知识
1.1线程池概念
在多线程当中,并发程度会不断提升,随之带来的是线程频繁的创建和销毁,此时程序的开销就会变大,为了改善这个现象,就引入了线程池,程序将创建好的线程放入到“线程池”中,当程序需要用时,可以直接在这个“池子”中取出来,线程用完之后再放入到“池子”中。这样在就不会再频繁的创建和销毁了,可以使程序开销减小,效率更高。
1.2线程池使程序更高效的原理
在创建、销毁线程时我们时是交给操作系统内核来完成的, 而我们使用线程池调度是以用户态实现的。
如果将任务交给操作系统的内核去完成,它的完成时间是不可控的,这是为什么呢?原因在于:内核需要对多个任务提供服务,在你交给它任务时,它可能无法仅仅单一的完成你交与的任务,可能还需要完成别人交与它的任务。
而用户态就不一样了,用户态就仅会对自己的任务负责,因此效率更高,开销更小。
🐣下面这个例子可以让你更加容易理解(银行办事):
有一天你去银行办理业务,排队排了好久,工作人员问你:拿身份证复印件了吗?你心想:卧槽还要这个。你回答:没有拿。然后工作人员说,没关系,你有两个选择:
1.我给你打印然后办理。(相当于内核态)
2.你自己打印然后给我。 (相当于用户态)
选择1:当让工作人员打印,可能他手头上出了你的打印东西在这个事还有其他的事,因此让他帮打印的话,打印好就不知道事猴年马月了。
选择2: 我自己直接去大厅的复印机这里,直接就打印好了,然后拿给工作人员,办完就跑。
很明显,在1、2选择中,选择2更高效,这也就是为什么线程池更高效了。
2.标准库中的线程池
2.1线程池的继承关系
2.2线程池的构造方法
Java中提供了线程池的标准类(ThreadPoolExecutor),构造方法(常用的一个)如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
对构造方法中参数解读
1. corePoolSize(核心线程);相对于公司的正式员工,签了劳动合同的,不能够随意开除。
2.maximumPoolSize (最大线程数):线程最多不能超过这这个数,最大线程数=核心线程+非核心线程;非核心线程就相当于公司招来的实现生。
3.keepAliveTime(非核心线程的存活时间):如果找来的实习生一直摸鱼没有活干,超过这个时间就将他炒鱿鱼。
4.unit:上方存活时间的单位
5.workQueue(用于缓存未执行的任务):线程的任务队列,通过submit方法加入的任务进入这个队列
6.threadFactory(线程工厂):线程的创建方案。
7.handler(拒绝策略):当任务对象线程满了的话,应该如何做出回答。
在Java总handler有四种拒绝策略:
2.3线程池的使用
线程池在实现时,我们需要用到工具类Executors,调用里面的静态方法来完成创建的,他的所有方法的返回值都是ExecutorService。
import java.util.concurrent.*; public class testDemo { public static void main(String[] args) { //创建一个固定数量的线程池 // 1. 创建一个操作无界队列且固定大小线程池 ExecutorService pool1 = Executors.newFixedThreadPool(10); //线程池中线程的数量是动态变化的 // 2. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存 ExecutorService pool2 = Executors.newCachedThreadPool(); //线程池中只有一个线程 // 3. 创建一个操作无界队列且只有一个工作线程的线程池 ExecutorService pool3 = Executors.newSingleThreadExecutor(); //线程池中只有一个线程+定时器功能 // 4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。 ExecutorService pool4 = Executors.newSingleThreadScheduledExecutor(Executors.defaultThreadFactory()); //创建一个固定数量的线程池+定时器功能 // 5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。 ExecutorService pool5 = Executors.newScheduledThreadPool(3, Executors.defaultThreadFactory()); // 6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务,不保证处理顺序 ExecutorService pool6 = Executors.newWorkStealingPool(); // 7. 自定义线程池 ExecutorService pool7 = new ThreadPoolExecutor(3, 10, 10000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } }
3.线程池的优点
- 1.降低资源消耗:减少线程的创建和销毁带来的性能开销。
- 2.提高响应速度:当任务来时可以直接使用,不用等待线程创建
- 3.可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。
4.线程池的模拟实现
核心操作为 submit, 将任务加入线程池中
使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
使用一个 BlockingQueue 组织所有的任务
每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了.
import java.util.concurrent.LinkedBlockingQueue; class Worker extends Thread { private LinkedBlockingQueue<Runnable> queue = null; public Worker(LinkedBlockingQueue<Runnable> queue) { super("worker"); this.queue = queue; } @Override public void run() { // try 必须放在 while 外头, 或者 while 里头应该影响不大 try { while (!Thread.interrupted()) { Runnable runnable = queue.take(); runnable.run(); } } catch (InterruptedException e) { } } } public class MyThreadPool { private int maxWorkerCount = 10; private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue(); public void submit(Runnable command) throws InterruptedException { if (queue.size() < maxWorkerCount) { // 当前 worker 数不足, 就继续创建 worker Worker worker = new Worker(queue); worker.start(); } // 将任务添加到任务队列中 queue.put(command); } }
严重线程池是否模拟实现完成 代码运行如下:
public static void main(String[] args) throws InterruptedException { MyThreadPool myThreadPool = new MyThreadPool(); for (int i = 0; i < 10; i++) { int j=i; myThreadPool.submit(new Runnable() { @Override public void run() { System.out.println("hello"+j); } }); Thread.sleep(1000); }
运行结果如下: