面试官问你线程池 几个参数?怎么创建?拒绝策略?

一、 入门肯定是要学线程的创建方式

1.1 继承Thread
package use;

/**
 * @author 发现更多精彩  关注公众号:木子的昼夜编程
 * 继承Thread类
 */
public class Method01 extends Thread{
    @Override
    public void run() {
        System.out.println("继承Thread...");
    }
}
1.2 实现Runnable
package use;

/**
 * @author 发现更多精彩  关注公众号:木子的昼夜编程
 * 实现Runnable接口
 */
public class Method02 implements Runnable{

    @Override
    public void run() {
        System.out.println("实现Runnable接口...");
    }
}
1.3 实现Callable
package use;

import java.util.concurrent.Callable;

/**
 * @author 发现更多精彩  关注公众号:木子的昼夜编程
 * 实现 Callable与Future
 * 有返回值+可抛异常
 */
public class Method03 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("实现Callable...");
        return 100;
    }
}

1.4 其他

匿名内部类、Timer、线程池、框架自带功能

package use;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author 发现更多精彩  关注公众号:木子的昼夜编程
 * 声明:掌握1、2、3 即可 线程池也可以了解下
 * 其他的方式作为会使用即可 面试不必先说出来 如果是个绝绝子面试官 你可以说说 看看是不是他想要的
 */
public class TestMethod {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1. 继承Thread
        new Method01().start();
        //2. 实现Runnable
        new Thread(new Method02()).start();
        //3. 实现Callable Thread只接受Runnable对象
        // 需要用FutureTask包装一下
        FutureTask<Integer> ft = new FutureTask<>(new Method03());
        new Thread(ft).start();
        Integer res = ft.get();
        System.out.println("返回值:"+res);
        // 4. 匿名内部类方式 我觉得这本质上还是实现Runnable接口 但是有的面试官喜欢问这些 (闲的没事儿干)
        new  Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类");
            }
        }).start();
        // 还可以这样
        new Thread(()->{
            System.out.println("我觉得这也算匿名内部类");
        }).start();

        // 5. java.util.Timer 绝绝子 也有人说这是一种实现方式
        // TimerTask是继承了Runable
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Timer...");
            }
        }, 0, 1000);
        // 6. 线程池方式  线程池我们后边细聊 但是你说他是一种创建方式 其实我觉得是不对的 还是基于前三种
        // 那你总不能说我自己写一个类 implements Runable 我就创造了一种创建线程的方式吧
        // 7. 框架自带的一些线程池实现方式
        // 比如Spring的 @EnableAsync+@Async  这也能实现多线程 但是他依旧是基于前三种来的
    }
}

二、线程池

2.1 故事先行

挠挠头,想一个故事带入一下.

某年某月某天,程序员小路觉得身体顶不住了,不准备写代码了,那他想干什么呢?

他想回家卖烤全羊。

烤全羊是一项很考验技术和经验的活儿,一个人同一时间只能烤2个养

刚开始生意不是很好,属于开拓疆土的阶段

面试官问你线程池  几个参数?怎么创建?拒绝策略?

这个时候如果来了第三波人,就需要排队了,排队会导致吃饭体验不好,这样可能就收不到5星好评了

所以在顾客量上来的时候小路招了几个临时工,让临时工烤羊,自己负责收钱

面试官问你线程池  几个参数?怎么创建?拒绝策略?

但是美中不足的是临时工不稳定,三天两头不干了,而且烤的羊味道也不好控制。

这个时候有什么好方法呢,那就招几个固定工吧。但是固定工人数是固定的,顾客下单多的话,还得让顾客排队

面试官问你线程池  几个参数?怎么创建?拒绝策略?

但是排队的人太多了不是很好呀,耽误赚钱,这时候小路想了一个方法,先让固定工烤,然后菜单最多堆积10个,超过十个之后就找临时工来,等订单量消耗完了,临时工就走了。

面试官问你线程池  几个参数?怎么创建?拒绝策略?

小路虽然年纪大了,但是还是个有血性的人,如果是个坏人来吃烤全羊,小路是不会给他做的,直接拒绝。

记住这个故事,等你了解了线程池的基本属性与方法后你就会发现这个线程池跟这个故事很像。

2.2 线程池体系架构

面试官问你线程池  几个参数?怎么创建?拒绝策略?

实线:表示继承

虚线:表示实现

  1. Executor :线程池的*接口 只有一个execute方法

  2. ExecutorService: 线程池第二大接口,对Executor做了一些扩展,加了一些方法
    比如shutdown、shutdownNow、submit等

  3. ScheduleExecutorService: 对ExecutorService 做了一些扩展,增加了定时任务相关的方法

  4. AbstractExecutorService: 抽象类,用模板方法设计模式实现了一部分方法。模板方法就是定义方法A、B、C

    子类就套用这些方法实现

  5. ThreadPoolExecutor: 普通线程池类,包含了线程基本的操作方法

  6. ScheduleThreadPoolExecutor:定时任务线程池类,用于实现定时任务相关功能

  7. ForkJoinPool:新型线程池类,Java7中新增的线程池类,基于工作窃取的理论实现,运用于大任务拆小任务,任务很多的场景。

  8. Executors:线程池工具类 ,这个在图中没有标出来 ,他就是一个工具类,跟我们平时写的DateUtil、StringUtil差不多,定义了一些快速实现线程池的方法。但是阿里规范里边说了,不让用这个(你牛,你说了算),我个人觉得看场景吧,首先你要先了解Executors提供的那几个方法都是什么意思,这样你才可以很好的选择使用哪种,而不是能用就行。

2.3 Executor
// 这个接口有什么意义呢 因为我不知道要怎么执行Runnable 所以我抽象出一个动作execute来 
// 等你知道怎么执行了 那好 你实现我这个接口 
public interface Executor {
	// 执行任务的方法  没有返回值
    void execute(Runnable command);
}
2.4 ExecutorService

Executor接口的强化,Executor只抽象了一个execute方法,但是执行那个方法需要很多其他的动作支持,ExecutorService就抽象了这些动作出来

// 
public interface ExecutorService extends Executor {

    // 立即关闭线程池 不接受新任务 但是已经提交的任务会执行完成
    void shutdown();

    // 立即关闭线程池 尝试停止正在运行的任务 未执行的任务将不再执行
    // 被停止的任务会以列表的形式返回 
    // 这个列表形式返回任务 我们是可以理解的 你不知行了 你得返回调用者这些没有执行的任务 
    // 这样调用者才能够对这些任务进行其他处理 
    List<Runnable> shutdownNow();
    
    // 检查线程池是否已经关闭了
    boolean isShutdown();

    // 检查线程池是否已经终止 只有在shutdown()或shutdownNow() 之后调用才可能返回true 
    boolean isTerminated();

    // 在指定时间内 线程池达到中止状态返回true 
    // 等你shutdown或者shutdownNow线程后,想知道到底有没有终止
    // 你可以设置一个时间 在这个时间段内 如果检测到线程池终止了就会返回true
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
	
    // 执行有返回值的任务 任务的返回值为task.call()返回值
    <T> Future<T> submit(Callable<T> task);

    // 执行有返回值的任务 返回值为result 
    // 任务执行完后 才有返回
    <T> Future<T> submit(Runnable task, T result);

    // 执行有返回值的任务的 返回值为null 
    //  任务执行完后 才有返回
    Future<?> submit(Runnable task);

   // 批量执行任务 只有这些任务都执行完后才会返回 
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

   //在执行时间段内批量执行任务  未执行的任务会被取消
    // timeout是所有任务执行的规定时间 
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    // 返回任意一个已经完成的任务的结果 没有执行的任务将会被取消
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    // 在执行时间段内如果有任务完成  返回任意一个已完成的任务的执行结果  未执行的任务将被取消
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
2.5 AbstractExecutorService

使用了模板方法设计模式 抽象的ExecutorService实现 也就是实现了部分功能

public abstract class AbstractExecutorService implements ExecutorService {
    // 模板方法
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        // 父类的方法 等子类实现 调用子类的实现
        execute(ftask);
        return ftask;
    }
    
    // 很简单创建了一个FutureTask
    // FutureTask就是个特殊的Runnable
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    
    // 很多模板方法 巴拉巴拉
    // 执行所有的Callable 那我们怎么执行Runable呢? 转换成Callable呗 
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        // 创建一个任务大小的List 存放Future
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            // 循环执行 这里会分多个线程执行 
            for (Callable<T> t : tasks) {
                RunnableFuture<T> f = newTaskFor(t);
                futures.add(f);
                execute(f);
            }
            // 循环阻塞 等执行结束
            for (int i = 0, size = futures.size(); i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    try {
                        // 阻塞 
                        f.get();
                    } catch (CancellationException ignore) {
                        // 如果是CancellationException 忽略
                    } catch (ExecutionException ignore) {
                        // 如果是 ExecutionException 忽略
                    }
                }
            }
            // 如果除了 CancellationException 和 ExecutionException 没有抛其他异常  这里会执行
            done = true;
            // 返回futures
            return futures;
        } finally {
            // 如果抛出其他异常 不执行done = true 
            // 但是会执行finally 这是finally的特性 一般我们在使用锁Lock的时候都要再finally中释放
            if (!done)
                // 如果有异常 取消没有执行完的任务
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }
	
    // 带超时任务的执行
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        // 转换为纳秒 
        long nanos = unit.toNanos(timeout);
        
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            // 先把任务放到 futures
            for (Callable<T> t : tasks)
                futures.add(newTaskFor(t));
			// 开始计算执行时间 
            final long deadline = System.nanoTime() + nanos;
            final int size = futures.size();

            // 放入线程池执行
            for (int i = 0; i < size; i++) {
                execute((Runnable)futures.get(i));
                // 这种用法在框架中经常用 也就是超时的计算方法
                nanos = deadline - System.nanoTime();
                // 如果到达超时时间 返回futures futures包含未执行的任务
                if (nanos <= 0L)
                    return futures;
            }
			// 没有超时get阻塞 
            for (int i = 0; i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    // 如果超时 直接返回futures
                    if (nanos <= 0L)
                        return futures;
                    try {
                        f.get(nanos, TimeUnit.NANOSECONDS);
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    } catch (TimeoutException toe) {
                        return futures;
                    }
                    nanos = deadline - System.nanoTime();
                }
            }
            done = true;
            return futures;
        } finally {
            // 如果有异常或超时   取消没有执行完的任务
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }
     // 任意一个
     public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
        try {
            // 调用doInvokeAny 
            return doInvokeAny(tasks, false, 0);
        } catch (TimeoutException cannotHappen) {
            // 没带超时时间 所以不会有TimeoutException异常
            // 作者很个性:cannotHappen
            assert false;
            return null;
        }
    }
    // 带超时时间的任意一个
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        // 调用doInvokeAny
        return doInvokeAny(tasks, true, unit.toNanos(timeout));
    }
    
    // 这个流程有点儿复杂 我简单画一下流程 简单流程 不是那么严禁
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                              boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
        // 判断null 判断大小
        if (tasks == null)
            throw new NullPointerException();
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
        // 创建一个任务大小的List存放Future 
        // 我们上边说过 Future是用来存放任务执行情况和执行结果的 
        // 所以每个任务都有一个Future与之对应
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
        // ExecutorCompletionService 再议
        ExecutorCompletionService<T> ecs =
            new ExecutorCompletionService<T>(this);

        try {
            
            ExecutionException ee = null;
            // 超时时间 如果有就当前时间+超时时间   如果没有就设置0L 
            // 很多源码的超时都是这样使用
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Iterator<? extends Callable<T>> it = tasks.iterator();
			
            // 提交一个任务给ecs
            futures.add(ecs.submit(it.next()));
            // 总数-1
            --ntasks;
            // 
            int active = 1;

            for (;;) {
                // ecs获取是否有执行结果
                Future<T> f = ecs.poll();
                // 如果没有 接着放任务
                if (f == null) {
                    if (ntasks > 0) {
                        --ntasks;
                        futures.add(ecs.submit(it.next()));
                        ++active;
                    }
                    else if (active == 0)
                        break;
                    else if (timed) {
                        // 超时时间 指定时间获取结果没有 就抛超时异常
                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                        if (f == null)
                            throw new TimeoutException();
                        // 
                        nanos = deadline - System.nanoTime();
                    }
                    else
                        // 如果没有超时就阻塞等待 take会阻塞 源码是用的:BlockingQueue
                        // 关于队列相关知识 我后期会写
                        f = ecs.take();
                }
                // 如果有返回结果了 直接获取返回结果 
                // 注意这里两个if 不是if else的关系 是顺序执行 
                // 我刚开始理解成if else 了 所有蒙了会儿
                if (f != null) {
                    --active;
                    try {
                        return f.get();
                    } catch (ExecutionException eex) {
                        ee = eex;
                    } catch (RuntimeException rex) {
                        ee = new ExecutionException(rex);
                    }
                }
            }

            if (ee == null)
                ee = new ExecutionException();
            throw ee;

        } finally {
            // 取消剩余任务 这里虽然是从0开始 取消所有
            // 但是 方法里边肯定会判断 已经执行完的就不管了
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
        }
    }
    
}

面试官问你线程池  几个参数?怎么创建?拒绝策略?

// Runnable(线程执行的内容)+Future(存储异步执行的结果 线程执行的如何了 怎么取消的 就需要Future)
public interface RunnableFuture<V> extends Runnable, Future<V> {
	// 
    void run();
}
// 实现了RunnableFuture
public class FutureTask<V> implements RunnableFuture<V> {
    
}

2.6 ThreadPoolExecutor

这个就比较复杂了

我先简单说一下面试可能问到的问题 然后下一篇再详细讲各种实现代码

面试问题1: 我们不用Executors.newXXX创建线程池 我们应该怎么创建

答: 我们可以用new ThreadPoolExecutor

面试问题2: ThreadPoolExecutor

答: corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler

面试问题3: 这些参数都代表什么意思

答:

线程池的线程是懒加载的,刚创建好线程池的时候是没有可用线程的,刚开始空闲idle线程为0

**int corePoolSize: **

核心线程数,线程会一直存活

只要线程池线程数小于corePoolSize 来了任务就会创建新线程

如果allowCoreThreadTimeOut = true 那么空闲的核心线程超过指定时间(keepAliveTime)也会关闭

int maximumPoolSize:

最大线程数,当核心线程数已到达最大,并且任务队列满了之后就继续创建线程来处理任务

总线程数(包括核心线程)最大只能创建maximumPoolSize

除核心线程外的其他线程空闲的时候会超时关闭

如果任务队列满了并且线程数已经等于maximumPoolSize 那么就会执行拒绝策略

long keepAliveTime:

超时关闭线程时间,超过核心线程数的线程都会执行超时关闭 , 如果设置了allowCoreThreadTimeOut =true

核心线程也会超时关闭

超时时间的单位

BlockingQueue workQueue:

存放任务的队列,如果线程超过核心线程数,会先将任务放到队列中

ThreadFactory threadFactory:

线程创建工厂 ,默认是DefaultThreadFactory 可以自定义 默认name是pool-x-thread-y

RejectedExecutionHandler handler :

拒绝策略 ,当线程数到达最大线程并且任务队列已经满了的时候,再来新任务就会被拒绝

或者是线程池被shutdown之后,这段时间来的任务也会被拒绝

面试问题4: 你可以说一下一个任务submit之后线程池执行了怎么样的逻辑吗?

其实这个具体的逻辑包括创建Worker是很复杂的,但是面试官一般想让你说的是个大体的流程

你可以回答一下整个大体流程,等面试官详细问你具体怎么判断核心线程数怎么保证安全的,这时候你再给面试官将那些底层代码逻辑(这些东西我会在后边文章中写一下)

答: 一张图(如果你是在线面试 你可以给面试官分享一下桌面 线上给面试官画一个图。如果你是现场面试,记得带着笔和纸,拿出纸画出来,这样比你语言表达更有效果(因为你语言表达面试官可能会理解偏差),面试官可能会更喜欢)

面试官问你线程池  几个参数?怎么创建?拒绝策略?

面试问题5:

答: 其实拒绝策略的数量可以很多很多,但是默认jdk帮实现了四种 ,而线程池默认使用的是AbortPolicy

面试官问你线程池  几个参数?怎么创建?拒绝策略?

AbortPolicy:

这个最简单,直接抛RejectedExecutionException

CallerRunsPolicy:

用调用线程池submit的线程跑,也就是用主线程跑新进来的方法 (这个我强烈不建议,万一把主线程跑坏了 后续业务就不能处理了)

DiscardOldestPolicy:

将最早进入队列的任务删除,再将新的任务放入队列 这就是典型的喜新厌旧。

DiscardPolicy:

悄无声息的丢掉这个新来的任务 这个也就有点儿太不人性了

自定义:

可以自定义处理,比如把没处理完的任务内容记录到日志或者业务表,进行定时补偿处理。

至于怎么定义,之后的文章会一一写来

面试问题6: 除了ThreadPoolExecutor 还用过其他的吗

有些面试官问了展示自己很牛(人家能当面试官肯定有自己牛的地方,千万别跟面试官过不去,不值当)

这时候面试官可能想了解你平时对于新知识的扩展

答: Jdk1.7后增加了ForkJoinPool 这个是基于“分而治之”的思想来实现的,适合计算密集型的任务

他还有任务窃取(虽然是窃取但是这个是个褒义词)Work-Stealing,线程处理完自己的任务后还会去帮别人处理。

这个技术也计划在后边文章写一下。

三、唠唠

下一篇写线程池ThreadPoolExecutor 的代码解析 从Submit开始

线程池一定要先了解他的整体流程,也就是面试题4的那个流程 然后看代码的时候整体思路是清晰的。

上一篇:Visual Studio Code for .Net Framework


下一篇:SQL之事务