跟面试官谈【线程池】


 近期听到了很多求职小伙伴的心声,线程池不是很了解,而且只知道如果运用,更别提深层研究了,导致每次都被面试官问的哑口无言。那我就做回天使吧,给大家系统一下。
 我的套路是从原理----应用----源码;让你彻底弄成线程池。

What?

  线程池,就是一个池子嘛,但是这个池子不是装水的,而是装线程的;
  想想我们平常水池的是啥,还不是为了用水的时候方便,节省了来回运水的时间;而且我们可以让水可以循环利用,水资源多么宝贵呀,节约了水资源;同理,当面试官再问你,线程池好处的时候,是不是就可以结合“水池作用”,跟面试官装一把了;

Why?

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. **提高响应速度。**当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. **提高线程的可管理性。**线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

分类?

  多线程是采用了Executor框架。具体的类图关系如下;ThreadPoolExecutor是线程池创建的核心类。然后线程池的具体创建方式如下几种:

在这里插入图片描述

1. newCachedThreadPool

1.1.what?

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

1.2 代码实例

public class CacheThread  {

    public static void main(String[] args) {
        ExecutorService cachThread= Executors.newCachedThreadPool();
        for (int i = 0; i <10 ; i++) {
           final int temp=i;

            cachThread.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"当前线程的==="+temp);
                }
            });
        }
    }

1.3 执行结果

在这里插入图片描述
由上图,我们也得出了结果:虽然线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

1.4 源码分析

在这里插入图片描述

1.5 缺点

 最大线程数为Integer.MAX_VALUE,可能会创建很多的线程,然后导致OOM异常;

2.newFixedThreadPool

2.1 What?

 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

2.2 代码实例

public class FixThread {
    public static void main(String[] args) {
    // 有5个线程
        ExecutorService fixThread= Executors.newFixedThreadPool(5);
        for (int i = 0; i <10 ; i++) {
            final  int temp=i;
            fixThread.execute(new Runnable() {
                @Override
                public void run() {
                   System.out.println(Thread.currentThread().getName()+"当前线程=="+temp);
                }
            });
        }
    }
}

2.3 结果

在这里插入图片描述
我们可以得出结论:因为线程池的大小是5,所以它的好处是可以循环利用线程,合理的分配资源,去执行任务;

2.4 源码分析

在这里插入图片描述
 通过源码,可以看出,它是核心线程数和最大线程数是相同的,采用的是LinkedBlockQueue<>队列;

2.5 缺点

LinkedBlockQueue<>没有设定范围,它的值为Inter.Max_Value,这样类似于无界队列,然后会存储很多的线程,造成OOM异常;

3. newScheduledThreadPool

3.1 what

 创建一个定长线程池,支持定时及周期性任务执行

3.2 代码示例

    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final  int temp=i;
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                     System.out.println(Thread.currentThread().getName()+"当前线程"+temp);
                }
            },4, TimeUnit.SECONDS);
        }
    }
}

3.3 执行结果

延迟4秒执行,运算出下面结果
在这里插入图片描述

3.4 源码分析

在这里插入图片描述
通过源码分析,它底层也是 调用的ThreadPoolExecutor。

3.5 缺点

  同newCachedThreadPool一样,最大线程数为Integer.MAX_VALUE,可能会创建很多的线程,然后导致OOM异常;

4、newSingleThreadExecutor

4.1 what?

 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

4.2 代码示例

public class SingleThread  {
    public static void main(String[] args) {
        ExecutorService singleThread= Executors.newSingleThreadExecutor();
        for (int i = 0; i <10 ; i++) {
            final  int temp=i;
            singleThread.execute(new Runnable() {
                @Override
                public void run() {
                     System.out.println(Thread.currentThread().getName()+"当前线程"+temp);
                }
            });
        }
    }
}

4.3 运行结果

 只有一个线程在执行;
在这里插入图片描述

4.4 源码分析

在这里插入图片描述

4.5 缺点

同newFixThreadPool一样,LinkedBlockQueue<>没有设定范围,它的值为Inter.Max_Value,这样类似于无界队列,然后会存储很多的线程,造成OOM异常;

线程池

 由上面的几中线程的创建方式,我们可以看出,他们底层都调用了 “ThreadPoolExecutor”,上面的方法都有弊端,所以我们可以用ThreadPoolExecutor然后自定义线程池,根据我们项目中的需要;

1.线程池原理

  1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
  2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  3. 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

在这里插入图片描述

2. 自定义线程池

【案例一】此时运行5个任务===最大线程数+队列数

public class ThreadPoolExcutor {
    public static void main(String[] args) {
    ThreadPoolExecutor threadPoolExecutor= new ThreadPoolExecutor(1,
                                                              2,
                                                             60L,
                                                            TimeUnit.SECONDS,
                                                            new ArrayBlockingQueue<>(3),
                                                             new ThreadPoolExecutor.AbortPolicy()
           );
           //此时运行5个任务,没有超过  最大线程数+队列数
        for (int i = 1; i <=5 ; i++) {
            final  int temp=i;
            taskThread t1=new taskThread("任务线程");
            threadPoolExecutor.execute(t1);
        }
    }
}
class  taskThread implements Runnable{
    private  String taskName;
    public  taskThread(String taskName){
        this.taskName=taskName;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"当前现成的任务"+taskName);
    }
}

运行的结果:
在这里插入图片描述
【案例二】:此时用了两个线程,进行了复用,但是我们如果尝试一下运行6个任务(超过了最大线程数+队列的数据);
运行结果:
在这里插入图片描述
由结果可知,我们也就更进一步的明白了线程池的运行原理,当我们执行的任务超过了 最大线程数+队列中的线程,然后线程池就会采用一些拒绝策略,进行拒绝;

3、 拒绝策略

  1. AbortPolicy(默认):直接抛出RejectedException异常,阻止系统正常运行;
  2. CallerRunsPolicy:不会抛弃任务也不会抛出异常,而是回退给调用者;
  3. DiscardOldestPolicy:丢弃等待最久的任务,然后把当前任务加入队列中;
  4. DiscardPolicy:直接丢弃任务,不做任何处理。

上面的例子中,我采用的是默认AbortPolicy的拒绝策略,然后直接抛异常,下面我演示一下用 “CallerRunsPolicy”进行调用,查看一下结果;
在这里插入图片描述

4. 合理配置线程池

 我们在项目中使用线程池需要自定义最大线程数,那我们可以根据以下两点进行判断;

1 . IO密集型: 即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的;
这时候我们可以设定:最大线程数=CPU核数 * 2;
2 CPU密集型:该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
这时候我们可以设定:最大线程数=CPU核数 ;

5.应用

 在项目中我们需要多个任务的处理的时候,比如 使用多线程去消费mq中的数据等等;

总结

 在跟面试官吹的时候,可以借鉴上面的思虑然后在加上在项目中的经验,调理清晰;让面试官也大开眼界,不选你,选谁!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值