线程池的引入
最开始,进程能够解决并发变成的问题.
但是由于频繁创建销毁进程,成本比较高.引入了轻量级进程->线程
如果创建/销毁线程的频率进一步提高,此时线程的创建与销毁开销也就不能忽视了.
所以就需要想办法优化此处的线程的创建销毁效率.
解决方案有两种:
1.引入轻量级线程->也称纤程/协程(协程本质,是程序员在用户态代码中进行调度,不是靠内核的调度器调度的~~,节省了很多调度上的开销).纯用户态代码就比在内核内操作更安全更可控.协程是在用户代码中,基于线程封装过来的.本质是程序员在用户态中代码调度,而非内核调度器.
2.线程池,把提前使用的线程准备好.用完了也不要释放而是以备下次使用以节省创建/销毁开销
线程池的最大好处就是减少每次启动,销毁线程的消耗.
标准库中的线程池
使用Executors.newFixedThreadPool(10)能创建出固定包含10个线程的线程池.
返回值为ExecutorService.
通过ExecutorService.submit可以注册一个任务到线程池中.
public class TestExecuorService { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(10); pool.submit(new Runnable() { @Override public void run() { System.out.println("hello"); } }); } }
Executor创建线程池的几种方式
newFixedThreadPool:创建固定线程数目的线程池
newCachedThreadPool:创建线程数目动态增长的线程池
newSingleThreadExecutor:创建只包含单个线程的线程池
newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令.是进阶版的Timer.
Executor本质上是ThreadPoolExecutor类的封装.
Executors:只是简单用一下. ThreadPoolExecutor:希望是高度定制化的.
下面来介绍重要的线程池创建方式:ThreadPoolExecutor.
它提供了更多的可选参数,可以进一步细化线程池行为的设定.
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
1.corePoolSize:核心线程数,类似于正式员工的数量.(正式员工,一旦录用,永不辞退).
2.maximumPoolSize:最大线程数.正式员工+临时工项目.(临时工:一段时间不干活,就被辞退)
3.keepAliveTime:临时工允许的空闲时间.(如果空闲时间超过这个时间阈值,就会销毁掉)
4.unit:keepAliveTime的时间单位,是秒,是分还是其它值.
5.workQueue:传递任务的阻塞队列.(和定时器类似,线程池中也持有很多任务,Runnable作为描述任务的主体).
6.threadFactory
创建线程的工厂,参与具体的创建线程工作.通过不同线程工厂创建出的线程相当于对一些属性进行了不同的初始化设置.
线程工厂例子:
class Point { public static Point makePointByXY(double x, double y) { Point p = new Point(); p.setX(x); p.setY(y); return p; } public static Point makePointByRA(double r, double e) { Point p = new Point(); p.setR(r); p.setA(a); return p; } }
这里的两个方法就称为工厂方法 .
如果把工厂方法放到一个其他的类里面,这个其它的类就叫做"工厂类".
总的来说,通过静态方法封装new操作,在方法内部设定不同的属性完成对象初始化.构造对象的过程就是"工厂模式".
通过这个工厂类,来创建线程对象(Thread对象),在这个类里提供方法,让方法封装new Thread操作,并给Thread一些属性.
7.RejectedExecutionHandler:拒绝策略,如果任务量超出公司的符合了接下来怎么处理.(在阻塞队列中,能够容纳的元素是有上限的)
1.AbortPolicy():超过负荷,直接抛出异常.(新旧任务都不再执行)
2.CallerRunsPolicy():调用者负责处理多出来的任务,即添加任务的线程负责执行.(新任务会执行,只不过不是线程池执行,而是由调用者执行)
3.DiscardOldestPolicy():丢弃队列中最老的任务.(执行新任务,抛弃旧任务)
4.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; public class MyThreadPoolExecutor { //用于保存线程,用于以后能取出线程并修改 private List<Thread> ThreadList = new ArrayList<>(); //用于保存任务的队列 private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000); //通过这个方法,把这个任务添加到线程池中. public void submit(Runnable runnable) throws InterruptedException { queue.put(runnable); } //通过n指定创建多少个线程 //创建了一个固定数量的线程池 public MyThreadPoolExecutor(int n) { for(int i = 0; i < n; i++) { Thread t = new Thread(() -> { try { //取出一个任务,并执行 Runnable runnable = queue.take(); runnable.run(); } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); ThreadList.add(t); } } public static void main(String[] args) throws InterruptedException { MyThreadPoolExecutor pool = new MyThreadPoolExecutor(4); for(int i = 0; i < 1000; i++) { int n = i; pool.submit(new Runnable() { @Override public void run() { //要执行的工作 System.out.println("执行任务 " + n + ", 当前线程为: " + Thread.currentThread().getId()); } }); } } }