每日一练进击大厂【DAY6】并发编程3

75 阅读3分钟

一、线程池原理

  1. 如果此时线程池中的工作线程小于核心线程数时,则直接创建线程去执行任务;
  2. 如果此时线程池中的工作线程等于核心线程数时,但是缓冲队列未满,任务将被放入缓冲队列;
  3. 如果此时线程池中的工作线程大于等于核心线程数,并且缓冲队列已满,线程池中线程数量小于最大线程数,创建新的线程来处理任务;
  4. 如果此时线程池中的工作线程达到最大线程数,缓冲队列已满,执行拒绝策略;
  5. 如果线程池中线程数量大于核心线程数,空闲线程时间超过临时线程的存活时间,线程终将被终止,这样线程池可以动态的调整线城市中的线程数。

二、线程池中核心参数

  • corePoolSize:核心线程的数量
  • maximumPoolSize:最大线程数 (临时线程数=maximumPoolSize-corePoolSize)
  • keepAliveTime:临时线程的存活时间
  • unit:临时线程的存活单位
  • ThreadFactory:线程工厂
  • defaultHandler:拒绝策略

三、创建线程池的方法

  1. 固定线程数量
ExecutorService executorService = Executors.newFixedThreadPool(10);
  1. 只有一个核心线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
  1. 可以缓存的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
  1. 可以延期执行心跳检测
ExecutorService executorService = Executors.newScheduledThreadPool(2);
  1. 工作窃取
ExecutorService executorService = Executors.newWorkStealingPool();

创建线程池的底层方法

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,5,10, TimeUnit.MINUTES,new ArrayBlockingQueue<>(20));

四、线程池的拒绝策略

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认)。
  2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
  3. TheadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

五、线程池的大小如何设置

CPU密集型任务(N+1):这种任务消耗的主要是CPU资源,可以将线程数设置为N(CPU核心数)+1,比CPU核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其他原因导致的任务暂停而带来的影响。 IO密集型任务(2N):这种任务应用起来系统会大部分的时候来处理IO交互,而线程在处理IO的时间段内不会占用CPU来处理,这时就可以将CPU交出给其他线程使用。

六、线程池的好处

  1. 降低资源消耗:重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  2. 提高响应速度:任务达到时,无需等待线程创建即可立即执行。
  3. 提高线程的可管理性:使用线程可以进行统一的分配,调优和监控。
  4. 提供更多更强大的功能:线程池具备可扩展性,允许开发人员向其中增加更多的功能。

七、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方法。


总结

当你累的时候,你想的又是什么呢?星光不问赶路人,时光不负有心人!