关于java:面试官问线程池-几个参数-拒绝策略

5次阅读

共计 10958 个字符,预计需要花费 28 分钟才能阅读完成。

一、入门必定是要学线程的创立形式

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 故事后行

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

某年某月某天,程序员小路感觉身材顶不住了,不筹备写代码了,那他想干什么呢?<br/>

他想回家卖烤全羊。<br/>

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

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

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

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

然而美中不足的是临时工不稳固,三天两头不干了,而且烤的羊滋味也不好管制。

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

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

小路尽管年纪大了,然而还是个有血性的人,如果是个好人来吃烤全羊,小路是不会给他做的,间接回绝。

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

2.2 线程池体系架构

实线:示意继承 <br/>

虚线:示意实现 <br/>

  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 的那个流程 而后看代码的时候整体思路是清晰的。


欢送关注公众号:木子的昼夜编程

正文完
 0