Java多线程基础-10:代码案例之定时器(一) +https://developer.aliyun.com/article/1520548?spm=a2c6h.13148508.setting.14.75194f0ethWdBZ
4、标准库提供的4种拒绝策略⭐
下面是标准库提供的四种拒绝策略。
- ThreadPoolExecutor.AbortPolicy 直接抛异常:如果线程池满了还在继续加任务,添加操作就直接抛出异常,新任务和老任务都执行不了了。(你一个礼拜周一到周日满课,天天早八到晚十,结果班长还要求你去打扫办公室。你一听这个消息,直接绷不住了,课也不上了,哇得一声嚎啕大哭。)
- ThreadPoolExecutor.CallerRunsPolicy 添加任务的线程自己负责执行这个任务,即在哪个线程中写了submit()或execute()等向线程池中添加任务的方法,就由哪个线程来执行它要添加的任务。(你直接怼回去:我才不去呢,要去你自己去。)
- ThreadPoolExecutor.DiscardOldestPolicy 丢弃最老的任务,即阻塞队列队首元素,不执行了,直接删除。(你看了一下课表,决定把最早要上的这一节课鸽了,去打扫。)
- ThreadPoolExecutor.DiscardPolicy 丢弃最新的任务,只做原来的任务。(你还是继续上你的课,打扫办公室这个任务就直接丢弃了。)
注意,这里线程池并没有依赖于阻塞队列的阻塞行为,而是通过额外实现其它逻辑来更好地处理这个场景的操作。就好比班长告诉你你要去打扫卫生,然后他就阻塞住了,他也干不了别的你也干不了别的……最好的情况应当是你立即给出答复。在线程池中,并不希望依赖“满了阻塞”,而更主要是利用“空了阻塞”。
关于各个拒绝策略的具体场景,可以参考这篇文章:
🔗线程池的拒绝策略
三、代码实现线程池
下面代码实现了一个固定线程数的简单的线程池:
import java.util.*; import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; class MyThreadPool{ //阻塞队列用来存放任务 private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); //提交任务 public void submit(Runnable runnable) throws InterruptedException { queue.put(runnable); } //实现一个固定线程数的线程池 public MyThreadPool(int n) { for (int i = 0; i < n; i++) { Thread t = new Thread(() -> { try { while (true) { //取出线程池中的一个任务 Runnable runnable = queue.take(); runnable.run(); } } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); } } } public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException { MyThreadPool myThreadPool = new MyThreadPool(10); for (int i = 0; i < 1000; i++) { int num = i; //lambda表达式的变量捕获规则 myThreadPool.submit(()->{ System.out.println("hello " + num); }); } Thread.sleep(3000); } }
运行结果:
打印1000次
1、代码解析
核心的数据结构是BlockingQueue,它用于存放各个可执行的任务(runnable):
class MyThreadPool{ //阻塞队列用来存放任务 private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); //... }
submit()就像生产者一样,给队列中添加任务:
1.
class MyThreadPool{ //向线程池中提交任务 public void submit(Runnable runnable) throws InterruptedException { queue.put(runnable); } //... }
在线程池内部有一组总数为n的工作线程,它就像消费者一样,不停地从队列中取任务然后执行。
主线程中传入n为10,即线程数固定为10。在线程池构造方法中,通过for循环创建了10个工作线程。这10个工作线程是并发执行,无序调度的。每一个线程的任务都是不断从阻塞队列中获取任务并执行。因此这里为了保证工作线程的活跃,不会在执行完一个任务后立即终止线程,需要给取任务、执行任务的操作加上while(true)。如果没有while(true),线程执行完一个任务后就会终止,导致线程池中的线程数量不足,无法处理后续的任务(运行结果中只能打印10次)。
class MyThreadPool{ //实现一个固定线程数的线程池 public MyThreadPool(int n) { for (int i = 0; i < n; i++) { Thread t = new Thread(() -> { try { while (true) { //取出线程池中的一个任务 Runnable runnable = queue.take(); runnable.run(); } } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); } } //... }
主线程中,创建出线程池,并通过循环向其中添加1000个任务。这1000个任务先后被添加到阻塞队列中,工作线程从阻塞队列中获取任务并执行。注意,这里num的创建是由于lambda表达式(或匿名内部类)的变量捕获规则,它要求lambda表达式中捕获到的变量必须是final或实际final的(即不能被更改),由于变量 i 被更改了,因此重新创建一个变量来保存 i,代替 i 来使用:
public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException { MyThreadPool myThreadPool = new MyThreadPool(10); for (int i = 0; i < 1000; i++) { int num = i; myThreadPool.submit(()->{ System.out.println("hello " + num); }); } Thread.sleep(3000); } }
2、实际开发中如何给线程池设置合适的线程数量
实际不同的程序中,线程需要干的活大不相同。一个线程池的线程数量设置成几是比较合适的?这需要结合具体的任务情况,测试而定。
CPU密集型任务,主要做一些计算工作,要在 CPU 上运行。假设一个极端情况,如果你的线程执行的全是使用CPU资源的任务,那么线程数就不应该超过CPU的核心数(指逻辑核心)。
IO密集型任务,主要是等待 IO 操作(等待读写硬盘,读写网卡等),不怎么消耗 CPU 资源。如果你的线程全是使用IO,线程数就可以设置很多,远远超出 CPU 核心数。
不过,实践中很少有这么极端的情况,具体要通过测试的方式来确定,选取一个性能上恰当且资源使用上也恰当的这样一个均衡的结果。
测试的大体思路是:运行程序,通过记录时间戳计算一下执行时间(平均值),同时监测资源使用状态。
四、总结:线程池的执行流程
1. 任务提交
当有任务需要执行时,将任务提交给线程池。
2. 队列处理
线程池会将提交的任务放入任务队列中。任务队列是一个缓冲区,用于存储等待执行的任务。不同类型的线程池可能使用不同的任务队列。
3. 线程调度
线程池中的线程会从任务队列中获取任务。线程池会根据配置的核心线程数、最大线程数、线程空闲时间等参数来决定是否创建新的线程,或者复用空闲的线程来执行任务。
4. 任务执行
线程池中的工作线程会执行从任务队列中获取的任务。每个线程执行一个任务后,会继续从任务队列中获取下一个任务,直到线程池关闭或者没有更多的任务可以执行。
5. 线程回收
如果线程池中的线程空闲时间超过设定的时间(线程空闲时间设置的过程中),则线程会被回收,以减少资源消耗。但是,核心线程不会被回收,线程池会保持至少核心线程数的线程处于运行状态。
6. 线程池关闭
当不再需要线程池时,应该显式地关闭线程池,释放相关资源。关闭线程池后,线程池将不再接受新的任务,并且会等待所有已提交的任务执行完毕。
总结来说,线程池的执行流程涉及任务提交、队列处理、线程调度、任务执行和线程回收等步骤。通过线程池的管理,我们可以更好地控制线程的创建和销毁,提高程序的性能和效率,并避免因为频繁创建和销毁线程而导致的资源浪费和性能下降。