线程池
线程池是一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。线程池最大的好处是减少了每次创建销毁线程的开销。
标准库中的线程池
Executors 创建线程池的几种方式
① 使用 Executors.newFixedThreadPool(5) 能创建出固定包含 5个线程的线程池
返回值类型为 ExecutorService
通过 ExecutorService.submit 可以注册一个任务到线程池中
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool01 { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(5); for (int i = 0; i < 20; i++) { pool.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } } }
② 使用 Executors.newCachedThreadPool()创建出的线程池对象的特点是线程池数目能够动态适应,随着往线程池里添加任务,这个线程池里的线程数量会根据需要自动被创建,创建之后也不会着急销毁,会在线程池里存在一段时间以备随时使用。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool2 { public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < 20; i++) { pool.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } } }
③使用 Executors.newSingleThreadExecutor(): 创建只包含单个线程的线程池(一般用的不多)
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool3 { public static void main(String[] args) { ExecutorService pool = Executors.newSingleThreadExecutor(); for (int i = 0; i < 20; i++) { pool.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } } }
④使用 Executors.newScheduledThreadPool():相当于是定时器 设定 延迟时间后执行命令,或者定期执行命令
import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class TheadPool4 { public static void main(String[] args) { ExecutorService pool = Executors.newScheduledThreadPool(5); for (int i = 0; i < 20; i++) { pool.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } } }
Executors 本质上是 ThreadPoolExecutor 类的封装,ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定。
ThreadPoolExecutor创建线程池
最后一个构造方法可以涵盖上面其他三个,所以我们重点来看最后一个构造方法。
参数详解:
corePoolSize:核心线程数------>我们如果把线程池理解成一个公司,核心线程数就相当于正式员工数量
maximumPoolSize:最大线程数------>正式员工 + 实习生
keepAliveTime:空余线程等待新任务的最大时间------>允许实习生摸鱼的最大时间(如果说正式员工可以摸鱼并且不会被开除,实习生不允许摸鱼)
unit:keepAliveTime的时间单位
workQueue:阻塞队列,用来存放线程池中的任务,可以根据需要来设置这里的队列是啥比如:如果需要优先级则可以设置为PriorityBlockingQueue 如果不需要优先级线程数目是恒定的则可以使用ArrayBlockingQueue 如果任务变动较大可以使用LinkedBlockingQueue
threadFactory:工厂模式的体现,这里使用threadFactory作为工厂类,由这个类负责创建线程,使用工厂类创建线程主要是为了在创建过程中可以很方便对线程的属性进行设置
handler:线程池的拒绝策略,一个线程池能够容纳的任务数量有上限,当持续向线程池中添加任务,一旦达到上限,不同的拒绝策略有不同的效果
拒绝策略:↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
问题讨论:使用线程池,设置多少线程数目合适呢???
解释:一个线程执行的代码主要有两类:①CPU密集型:代码主要是进行算数运算/逻辑判断 ②IO密集型:代码主要进行的是IO操作
假设一个线程的所有代码都是CPU密集型代码,这时线程池数量不应该超过N(CPU逻辑核心数),即使超过N也无法提高效率,CPU占满了增加线程反而会增加调度的开销。
假设一个线程的所有代码都是IO密集型,这时不占CPU,此时设置线程数就可以超过N,一个核心可以通过调度来并发执行。
但是纯CPU密集型和纯IO密集型代码一般工作中不会出现,所以线程池的线程数量应该对程序进行测试。
模拟实现线程池
模拟实现线程池我们需要知道线程池里有什么:
1、线程池管理器(ThreadPoolManager):用于创建并管理线程池
2、工作线程(WorkThread): 线程池中线程
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行
4、任务队列:用于存放没有处理的任务。提供一种缓冲机制
核心操作为 submit, 将任务加入线程池中,使用一个 BlockingQueue 组织所有的任务 。
模拟实现一个简单版的线程池👇👇👇
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; //模拟实现线程池 public class MyThreadPool { //定义一个任务队列 private BlockingQueue<Runnable> deque = new ArrayBlockingQueue<>(1000); //把任务添加到队列中 public void submit(Runnable runnable) throws InterruptedException { deque.put(runnable); } public MyThreadPool(int n) { //创建出n个线程负责上述队列中的任务 for (int i = 0; i < n; i++) { Thread t = new Thread(() -> { //让线程消费任务并执行 try { Runnable runnable = deque.take(); runnable.run(); } catch (InterruptedException e) { throw new RuntimeException(e); } }); t.start(); } } }