两大线程模型
用户模型
通过应用接口来调用内核空间的cpu
内核模型
操作系统底层自身的线程,对于用户模型的运行无感知,但是提供接口对自己的调用,jvm线程是内核级线程
为什么使用线程池
大量线程下,会容易导致用户线程对内核线程的控制消耗很大,来回的上下文切换浪费大量时间和内存
,为了避免资源过度浪费,我们需要想出一个可以重用线程执行的任务,比如缓存这种,避免了频繁创造和销毁线程,这就是线程池的池化技术
线程池的优势
首先是,可以重用线程,避免频繁创造和销毁,减少内存消耗
其次是,不需要等待随拿随用,速度块
最后是,可以利用线程池对线程进行统一的管理,还有分配调优监控
最常用的线程工具类
Executors
三种常见的线程池
newFixedThreadPool(5) | newSingleThreadExecutor() | newCachedThreadPool() | newWordStealingPool() |
---|---|---|---|
这里面是固定了线程数量 | 单个线程 | 遇弱则弱,遇强则强 | 任务窃取,根据核数确定个数 |
可以缓存一些线程提高速度 | 固定了线程执行顺序 | 灵活性很高 | 适合耗时大量任务的场景 |
一般来说是自己配,因为线程池创建的参数最大线程数可以为Integer.MAX_VALUE,容易发生危险
我们应当使用ExecutorService自己创建自定义线程池
int runtime=Runtime.getRuntime().availableProcessors();
ExecutorService threadpool=new ThreadPoolExecutor(
2,
runtime,
3,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() //银行满了,还有人进来,不处理这个人的,抛出异常
);
try {
for (int i = 0; i < 9; i++) {
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"启动了");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadpool.shutdown();
}
自定义线程池七大参数
- int corePoolSize
银行的空闲办理业务的窗口 - maxmumPoolSize
业务最忙的时候的窗口 - keepAliveTime
业务忙多开的线程能保存的时间 - TimeUnit
保存时间单位 - BlockingQueue
阻塞队列,候客区,平时空闲先去候客区,满了再开启忙的时候的窗口 - ThreadFactory
创建线程的工厂一般使用默认 - RejectExecutionHandler
拒绝策略- 当所有窗口都满就会触发策略,有点像大堂经理
- AbortPolicy 丢出异常
- DiscarPolicy 丢弃任务
- DiscardOldestPolicy 尝试和第一去竞争,再重新执行
- CallerRunPolicy 哪里来哪里执行
- 自定义的话实现这个RejectExecutionHandler接口,然后重写rejectExecution这个方法即可
最多接收线程数=maxmumPoolSize+BlockingQueue
线程池执行流程图
线程池状态
线程池生命状态
Running | 能接收新任务和处理任务 |
---|---|
Shutdown | 不接收新任务,但可以处理有的任务 |
stop | 什么都不干,并且打断你处理任务 |
Tidying | 所有线程终止,ctl=0(这是记录状态和任务数量的标记) |
Terminated | 线程彻底终止,即线程池为Terminated状态 |
如何设置线程池合理的大小
cpu密集型任务
看看机器能支持多少并发
比较消耗cpu资源,大多是计算任务
CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。
可以设置为核数+1
io密集型
频繁i/o交互,不占用cpu资源,
不要让CPU闲下来,应加大线程数量,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
一般是2*核数+1
如果是定时线程池任务
等待的时间cpu处于空闲,更应该加大线程数量,而这个等待时间不确定,所以开放的数量得根据时间得变化而确定
一般是
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。
可以得出一个结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
原码分析
构造方法
execute核心方法
addworker方法
将当前线程加入work类
Work类
runWork方法
可以看出是怎么拿到线程然后执行线程的run方法的!