JAVA 线程池:干货—面试必背

线程池实现原理:

当向线程池提交了一个任务之后,线程池的处理流程如下:

  1. 判断是否达到核心线程数,若未达到,则直接创建新的线程处理当前传入的任务,否则进入下个流程

  2. 线程池中的工作队列是否已满,若未满,则将任务丢入工作队列中先存着等待处理,否则进入下个流程

  3. 是否达到最大线程数,若未达到,则创建新的线程处理当前传入的任务,否则交给线程池中的饱和策略进行处理。

具体实现

jdk 中 java.util.concurrent.ThreadPoolExecutor 提供了实现线程池的构造方法在

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

  1. corePoolSize:核心线程大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使有其他空闲线程可以处理任务也会创新线程,等到工作的线程数大于核心线程数时就不会在创建了。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前把核心线程都创造好,并启动。
  2. maximumPoolSize:线程池允许创建的最大线程数。如果队列满了,并且以创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。如果我们使用了无界队列,那么所有的任务会加入队列,这个参数就没有什么效果了。
  3. keepAliveTime:线程池的工作线程空闲后,保持存活的时间。如果没有任务处理了,有些线程会空闲,空闲的时间超过了这个值,会被回收掉。如果任务很多,并且每个任务的执行时间比较短,避免线程重复创建和回收,可以调大这个时间,提高线程的利用率。
  4. unit:keepAliveTIme的时间单位,可以选择的单位有天、小时、分钟、毫秒、微妙、千分之一毫秒和纳秒。类型是一个枚举java.util.concurrent.TimeUnit,这个枚举也经常使用,有兴趣的可以看一下其源码
  5. workQueue:工作队列,用于缓存待处理任务的阻塞队列,常见的有4种:
    1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按照先进先出原则对元素进行排序
    2. LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按照先进先出排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool使用了这个队列。
    3. SynchronousQueue :一个不存储元素的阻塞队列,每个插入操作必须等到另外一个线程调用移除操作,否则插入操作一直处理阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用这个队列
    4. PriorityBlockingQueue:优先级队列,进入队列的元素按照优先级会进行排序
  6. threadFactory:线程池中创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字
  7. handler:饱和策略,当线程池无法处理新来的任务了,那么需要提供一种策略处理提交的新任务,默认有4种策略:
    1. AbortPolicy:直接抛出异常
    2. CallerRunsPolicy:在当前调用者的线程中运行任务,即随丢来的任务,由他自己去处理
    3. DiscardOldestPolicy:丢弃队列中最老的一个任务,即丢弃队列头部的一个任务,然后执行当前传入的任务
    4. DiscardPolicy:不处理,直接丢弃掉,方法内部为空

    自定义创建线程的工厂

    给线程池中线程起一个有意义的名字,在系统出现问题的时候,通过线程堆栈信息可以更容易发现系统中问题所在。自定义创建工厂需要实现java.util.concurrent.ThreadFactory接口中的Thread newThread(Runnable r)方法,参数为传入的任务,需要返回一个工作线程。

示例代码:`import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo4 {
static AtomicInteger threadNum = new AtomicInteger(1);

public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
            60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue(10), r -> {
        Thread thread = new Thread(r);
        thread.setName("自定义线程-" + threadNum.getAndIncrement());
        return thread;
    });
    for (int i = 0; i < 5; i++) {
        String taskName = "任务-" + i;
        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "处理" + taskName);
        });
    }
    executor.shutdown();
}

自定义饱和策略

需要实现RejectedExecutionHandler接口。任务无法处理的时候,我们想记录一下日志,我们需要自定义一个饱和策略,示例代码:
`

public class Demo5 {
static class Task implements Runnable {
String name;

    public Task(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "处理" + this.name);
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return "Task{" +
                "name='" + name + '\'' +
                '}';
    }
}

public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
            1,
            60L,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue(1),
            Executors.defaultThreadFactory(),
            (r, executors) -> {
                //自定义饱和策略
                //记录一下无法处理的任务
                System.out.println("无法处理的任务:" + r.toString());
            });
    for (int i = 0; i < 5; i++) {
        executor.execute(new Task("任务-" + i));
    }
    executor.shutdown();
}

输出结果中可以看到有3个任务进入了饱和策略中,记录了任务的日志,对于无法处理多任务,我们最好能够记录一下,让开发人员能够知道。任务进入了饱和策略,说明线程池的配置可能不是太合理,或者机器的性能有限,需要做一些优化调整。

线程池的关闭方法

线程池提供了2个关闭方法:shutdown和shutdownNow,当调用者两个方法之后,线程池会遍历内部的工作线程,然后调用每个工作线程的interrrupt方法给线程发送中断信号,内部如果无法响应中断信号的可能永远无法终止,所以如果内部有无线循环的,最好在循环内部检测一下线程的中断信号,合理的退出。调用者两个方法中任意一个,线程池的isShutdown方法就会返回true,当所有的任务线程都关闭之后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。

调用shutdown方法之后,线程池将不再接口新任务,内部会将所有已提交的任务处理完毕,处理完毕之后,工作线程自动退出。

而调用shutdownNow方法后,线程池会将还未处理的(在队里等待处理的任务)任务移除,将正在处理中的处理完毕之后,工作线程自动退出。

至于调用哪个方法来关闭线程,应该由提交到线程池的任务特性决定,多数情况下调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

你可能感兴趣的:(学习ING)