多线程案例四
线程池是什么
想象这么⼀个场景:在学校附近新开了⼀家快递店,⽼板很精明,想到⼀个与众不同的办法来经营。店⾥没有雇⼈,⽽是 每次有业务来了,就现场找⼀名同学过来把快递送了,然后解雇同学。这个类⽐我们平时来⼀个任 务,起⼀个线程进⾏处理的模式。
很快⽼板发现问题来了,每次招聘 + 解雇同学的成本还是⾮常⾼的。⽼板还是很善于变通的,知道 了为什么⼤家都要雇⼈了,所以指定了⼀个指标,公司业务⼈员会扩张到 3 个⼈,但还是随着业务 逐步雇⼈。于是再有业务来了,⽼板就看,如果现在公司还没 3 个⼈,就雇⼀个⼈去送快递,否则 只是把业务放到⼀个本本上,等着 3 个快递⼈员空闲的时候去处理。这个就是我们要带出的线程池的模式。
这样,老板就节省了每次招聘和解雇人的开销,同理,线程池最大的好处就是减少每次启动、销毁线程的损耗。
标准库中的线程池
使⽤ Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池,返回值类型为 ExecutorService,通过 ExecutorService.submit 可以注册⼀个任务到线程池中。
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}
});
Executors 创建线程池的⼏种⽅式
newSingleThreadExecutor: 创建只包含单个线程的线程池:
SingleThreadExecutor被称为单线程线程池,这个线程池只有一个线程在工作,相当于单线程串行执行所有任务。任务按照指定顺序执行,且任务间的执行互斥。
newFixedThreadPool: 创建固定线程数的线程池:
FixedThreadPool被称为固定大小线程池。该线程池中的线程数量是固定的,当线程池中的线程都处于活跃状态时,新任务会等待可用空闲线程。若所有线程都忙碌,新任务会在阻塞队列中等待。
newCachedThreadPool: 创建线程数⽬动态增⻓的线程池:
CacheThreadPool被称为缓存线程池。该线程池中的线程数是可以无限增加的,当线程空闲时可以对线程进行回收销毁。
newScheduledThreadPool: 设定 延迟时间后执⾏命令,或者定期执⾏命令. 是进阶版的 Timer:
Executors 本质上是 ThreadPoolExecutor 类的封装
corePoolSize: 正式员工的数量. (正式员工, ⼀旦录用, 永不辞退)。
maximumPoolSize: 正式员工 + 临时⼯的数目. (临时工: ⼀段时间不⼲活, 就被辞退)。
keepAliveTime: 临时工允许的空闲时间。
unit: keepaliveTime 的时间单位, 是秒, 分钟, 或者其他值。
workQueue: 传递任务的阻塞队列。
threadFactory: 创建线程的工厂, 参与具体的创建线程⼯作. 通过不同线程工厂创建出的线程相当于
对⼀些属性进行了不同的初始化设置。
RejectedExecutionHandler: 拒绝策略, 如果任务量超出公司的负荷了接下来怎么处理:AbortPolicy(): 超过负荷, 直接抛出异常。CallerRunsPolicy(): 调⽤者负责处理多出来的任务。DiscardOldestPolicy(): 丢弃队列中最⽼的任务。DiscardPolicy(): 丢弃新来的任务。
下面实现一个创建固定线程数的线程池:
核⼼操作为 submit, 将任务加⼊线程池中。
使⽤ Worker 类描述⼀个⼯作线程. 使用Runnable 描述⼀个任务。
使⽤⼀个 BlockingQueue 组织所有的任务。
每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执⾏。
指定⼀下线程池中的最⼤线程数 maxWorkerCount; 当前线程数超过这个最⼤值时, 就不再新增
线程。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;class MyThreadPoolExecutor {//记录线程池中的线程private List<Thread> threadList = new ArrayList<Thread>();//将任务放到具有阻塞功能的队列中private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);//创建固定数量的线程池public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();threadList.add(t);}}//添加任务到线程池public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}
}
public class ThreadDemo17 {public static void main(String[] args) throws InterruptedException {MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(4);for (int i = 0; i < 1000; i++) {int n = i;myThreadPoolExecutor.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务" + n + ",当前线程:" + Thread.currentThread().getName());}});}}
}
结果如下(只有一部分):
可以看出,任务按顺序入队,却并非按照顺序执行,归根结底,还是抢占式执行的原因。
总结:
在Java中,线程池是一种管理和复用线程的机制,用于提高多线程应用程序的性能和资源利用率。线程池在执行任务时,可以避免频繁地创建和销毁线程,从而减少了系统开销,并且能够更有效地利用系统资源。主要作用如下:
① 线程的复用:线程池会预先创建一定数量的线程,并将它们保存在池中。当有任务需要执行时,线程池会分配一个空闲的线程来执行任务,执行完毕后线程不会销毁,而是重新放入线程池中,等待下一个任务的到来。这种线程的复用避免了频繁地创建和销毁线程,提高了线程的利用率。
② 资源管理:线程池可以限制并发线程的数量,避免系统因为线程过多而导致资源耗尽或性能下降的问题。通过配置线程池的核心参数,可以控制线程数量的上限、空闲线程的存活时间等,从而更好地管理系统资源。
③ 任务调度:线程池可以用于执行各种类型的任务,包括周期性任务、延迟任务、定时任务等。通过使用不同类型的线程池和调度策略,可以灵活地调度任务的执行时间和执行方式,满足不同场景下的需求。