1. 线程池简介
Java线程池是一种用于管理和重用线程的机制,它可以在需要执行任务时,从线程池中获取线程,执行任务,然后将线程放回池中,以便后续使用。线程池可以有效地管理线程的数量,提高程序的性能和资源利用率。
为什么从线程池里面取线程比直接创建线程快呢?
创建线程是要在操作系统内核中完成的,涉及"用户态"到"内核态"切换操作. 这个切换是有一定开销的. 而线程池取线程是纯的用户态实现.
2. 创建线程池
注意这里线程池创建的写法:
ExecutorService executorService = Executors.newCachedThreadPool();
这里并没有new,而是通过Executors的静态方法newCachedThreadPool来创建的,这里涉及到一种设计模式-“工厂模式”
3. 工厂模式简介
工厂模式(Factory Pattern)是一种创建对象的设计模式,它提供了一种封装对象创建过程的方式,使得客户端代码与具体对象的实例化过程解耦。
工厂模式的主要目的是通过一个工厂类来创建对象,而不是直接在客户端代码中使用new关键字来实例化对象。这样可以隐藏具体对象的创建细节,提供更灵活、可扩展的设计。
详细可参考:工厂模式 | 菜鸟教程
4. 线程池的使用
线程池的使用很简单,只需要实现submit方法即可
public class Demo19 { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(new Runnable() { @Override public void run() { System.out.println("线程执行的任务1"); } }); executorService.submit(new Runnable() { @Override public void run() { System.out.println("线程执行的任务2"); } }); executorService.submit(new Runnable() { @Override public void run() { System.out.println("线程执行的任务3"); } }); } }
这里主要是将任务放到线程池中,线程池里的线程就会去执行这些任务
运行结果:
5. 实现线程池
要想实现线程池,要先弄清楚线程池有哪些功能.
一个线程池中可以同时提交N个任务,线程池里有M个线程来执行这N个任务
如何将N个任务分配给M个线程呢?
可以使用生产者-消费者模型解决这个问题.
以下是一个线程池的简易实现:
public class MyThreadPool { private BlockingQueue<Runnable> blockingQueue = new public class MyThreadPool { private BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>(); public void submit(Runnable runnable) throws InterruptedException { // 将任务添加到阻塞队列中 blockingQueue.put(runnable); } public MyThreadPool(int m){ // 创建m个线程 for (int i = 0; i < m; i++) { Thread t = new Thread(() ->{ // 让它一直扫描阻塞队列 while (true) { try { // 取出任务,并执行 Runnable runnable = blockingQueue.take(); runnable.run(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); } } }
测试上述代码:
public class Demo20 { public static void main(String[] args) throws InterruptedException { MyThreadPool pool = new MyThreadPool(5); for (int i = 0; i < 10; i++) { int temp = i; pool.submit(new Runnable() { @Override public void run() { System.out.println("任务"+ temp); } }); } } }
运行结果:
不过上述代码中创建的线程数量是"固定"的,可根据需要自行调整
6. ThreadPoolExecutor的构造方法讲解
ThreadPoolExecutor是Java中线程池的一个具体实现类,它实现了ExecutorService接口。ExecutorService是一个高级的线程池管理接口,提供了更多的灵活性和功能。
而ThreadPoolExecutor这个类的构造方法比较复杂
参数:
corePoolSize - 即使空闲时仍保留在池中的线程数,除非设置 allowCoreThreadTimeOut
maximumPoolSize - 池中允许的最大线程数
keepAliveTime - 当线程数大于内核时,这是多余的空闲线程在终止前等待新任务的最大时间。
unit - keepAliveTime参数的时间单位
workQueue - 用于在执行任务之前使用的队列。 这个队列将仅保存execute方法提交的Runnable任务。(自己传入的任务队列,如果传了就用传入的队列.如果不传,就线程池就去创建)
threadFactory - 执行程序创建新线程时使用的工厂
handler -任务执行被拒绝时使用的处理程序,因为达到线程限制和队列容量
任务执行被拒绝时使用的程序有以下四个:
实际开发中,主要根据需求选择这里的拒绝策略进行处理.
7. 线程池的线程数量,如何确定?
线程池可以自定义线程的数量
那么在实际开发中,线程池的线程数量设置成多少比较合适?
这个问题主要取决于以下几点:
1.任务类型:不同类型的任务对线程数量的需求不同。如果是CPU密集型任务,通常线程数量应该与CPU核心数相近,以充分利用CPU资源。如果是IO密集型任务,可以设置较多的线程数量,以便处理并发的IO操作。
2.系统资源:需要考虑系统的硬件资源和性能。如果系统资源有限,例如内存、CPU等,线程数量应适当控制,避免过多的线程导致资源竞争和性能下降。
3.并发量:需要根据任务的并发量来确定线程数量。如果任务并发量较高,可以增加线程数量以提高任务处理能力。如果任务并发量较低,可以减少线程数量以节省资源。
4.响应时间:需要考虑任务的响应时间要求。如果任务需要快速响应,可以增加线程数量以减少等待时间。如果任务的响应时间可以接受较长的延迟,可以减少线程数量以节省资源。
5.线程池类型:不同类型的线程池有不同的线程数量限制。例如,FixedThreadPool固定线程池的线程数量是固定的,CachedThreadPool可缓存线程池的线程数量是动态调整的。
这个问题要考虑的因素有很多,最简单的方法就是去测试
将线程分为设置成不同的数量,然后观察系统的性能和任务的执行情况,根据需要逐步调整线程数量,找到最优的配置。