一、线程池原理
- 如果此时线程池中的工作线程小于核心线程数时,则直接创建线程去执行任务;
- 如果此时线程池中的工作线程等于核心线程数时,但是缓冲队列未满,任务将被放入缓冲队列;
- 如果此时线程池中的工作线程大于等于核心线程数,并且缓冲队列已满,线程池中线程数量小于最大线程数,创建新的线程来处理任务;
- 如果此时线程池中的工作线程达到最大线程数,缓冲队列已满,执行拒绝策略;
- 如果线程池中线程数量大于核心线程数,空闲线程时间超过临时线程的存活时间,线程终将被终止,这样线程池可以动态的调整线城市中的线程数。
二、线程池中核心参数
- corePoolSize:核心线程的数量
- maximumPoolSize:最大线程数 (临时线程数=maximumPoolSize-corePoolSize)
- keepAliveTime:临时线程的存活时间
- unit:临时线程的存活单位
- ThreadFactory:线程工厂
- defaultHandler:拒绝策略
三、创建线程池的方法
- 固定线程数量
ExecutorService executorService = Executors.newFixedThreadPool(10);
- 只有一个核心线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
- 可以缓存的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
- 可以延期执行心跳检测
ExecutorService executorService = Executors.newScheduledThreadPool(2);
- 工作窃取
ExecutorService executorService = Executors.newWorkStealingPool();
创建线程池的底层方法
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,5,10, TimeUnit.MINUTES,new ArrayBlockingQueue<>(20));
四、线程池的拒绝策略
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认)。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- TheadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。
五、线程池的大小如何设置
CPU密集型任务(N+1):这种任务消耗的主要是CPU资源,可以将线程数设置为N(CPU核心数)+1,比CPU核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其他原因导致的任务暂停而带来的影响。 IO密集型任务(2N):这种任务应用起来系统会大部分的时候来处理IO交互,而线程在处理IO的时间段内不会占用CPU来处理,这时就可以将CPU交出给其他线程使用。
六、线程池的好处
- 降低资源消耗:重复利用已创建的线程,降低线程创建和销毁造成的消耗。
- 提高响应速度:任务达到时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:使用线程可以进行统一的分配,调优和监控。
- 提供更多更强大的功能:线程池具备可扩展性,允许开发人员向其中增加更多的功能。
七、ThreadLocal
定义:ThreadLocal叫做线程变量,ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程都可以访问自己内部的副本变量。 使用场景:线程间数据隔离、进行事务操作,用于存储线程事务信息、数据库连接,session会话管理、在进行对象跨层传递的时候。 底层数据结构:Hash表、数组,在ThreadLocal里面数组下标是通过key.hashcode*魔数+魔数=最终的hash&(n-1)。这个数组默认长度是16,扩容因子2/3;
public class ThraedLocalDemo {
static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0; //初始化一个值
}
};
public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(() -> {
int num = local.get(); //每一个线程获得的值都是0
local.set(num += 5);
System.out.println(Thread.currentThread().getName() + "=" + num);
});
}
for (int i = 0; i < 5; i++) {
threads[i].start();
}
}
}
八、ThreadLocal实现原理
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,即每个线程都有一个属于自己的ThreadLocalMap。 ThreadLocalMap内部维护者Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
九、ThreadLocal怎么解决hash冲突和内存泄漏
解决hash冲突: 通过线程探测、斐波那契散列去保证尽可能的去减少hash冲突。
解决内存泄漏: key为弱引用,因为用了弱引用,只会去清除我们的key,但是entry还是存在的,所以要即使清理无效的entry,保证entry不会发生溢出,及时remove。
强引用 Object obj = new Object(); Object obj2 =obj; //强引用 垃圾回收的时候不会被回收
弱引用 垃圾回收的时候会被回收 Object obj = new Object(); WeakReference weakReference = new WeakReference(obj ); // 弱引用
软引用 发生GC,如果内存足够就不会回收,不足就会回收。 Object obj = new Object(); SoftReference softReference = new SoftReference(obj ); // 软引用
十、submit()和execute()方法的区别
两个方法都可以向线程池提交任务,execute方法的返回类型是void,它定义在Executor接口中。而submit方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其他线程池像ThreadPoolExecutor和ScheduledThreadExecutor用的都是submit方法。
总结
当你累的时候,你想的又是什么呢?星光不问赶路人,时光不负有心人!