线程池技术

注:部分图片来自网络,如侵必删!

线程池技术

具有定时任务的线程池:ScheduledThreadPoolExecutor

普通的线程池:ThreadPoolExecutor

请添加图片描述

并发编程三要素

  1. 原子性:不可分割的操作,多个步骤要保证同时成功或者同时失败(synchronized)
  2. 有序性:程序执行的顺序和代码的顺序要保持一致(volatile)
  3. 可用性:一个线程对共享变量的修改,另一个线程能立马看到(volatile)

ThreadPoolExecutor 线程池

构造方法

public ThreadPoolExecutor(int corePoolSize, // 核心线程数,最多保留的线程数
                          int maximumPoolSize, // 最大线程数
                          						// 救急线程数 = 最大线程数 - 核心线程数
                          long keepAliveTime, // 生存时间-针对救急线程
                          TimeUnit unit, // 时间单位-针对救急线程
                          BlockingQueue<Runnable> workQueue, // 阻塞队列
                          ThreadFactory threadFactory, // 线程工厂
                          RejectedExecutionHandler handler) { // 拒绝策略
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

线程类型

核心线程、救急线程(都是懒惰性初始化)

maximumPoolSize:最大线程数

corePoolSize:核心线程数

maximumPoolSize - corePoolSize:救急线程数,所谓救急线程就是指,核心线程和阻塞队列都无法处理过多的任务时,线程池就会去创建救急线程来帮忙,如果在keepAliveTime的时间内没有任务到来,就自动销毁

例如:下图中,核心线程和阻塞队列都满了,此时如果添加了新的任务5,线程池就会创建一个救急线程去处理任务5,处理完任务5之后,在keepAliveTime的时间内没有新的任务到来,线程池就会销毁该线程

请添加图片描述

线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

状态名高 3 位接收新任务处理阻塞嘟列任务说明
RUNNING111yesyes
SHUTDOWN000nono不会接收新任务,但会处理阻塞队列的剩余任务
STOP001nono会中断正在执行的任务,并抛弃阻塞队列的任务
TIDYING010--任务全执行完毕,活动线程为 0 即将进入终结
TERMINATED011--终结状态

ScheduledThreadPoolExecutor 任务调度线程池

构造方法

从构造方法中,我们不难看出:ScheduledThreadPoolExecutor 和 ThreadPoolExecutor 的区别。

  • 最大线程数为 Integer.MAX_VALUE,意味着救急线程可以无限创建

  • 使用延迟队列 DelayedWorkQueue,这是实现 ScheduledThreadPoolExecutor 任务调度线程池最核心的地方

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}

核心方法

方法名概念
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)让任务延迟 delay unit 时间后执行,Runnable 表示无返回值
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)让任务延迟 delay unit 时间后执行,Callable 表示有返回值
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)让任务延时 initialDelay 后以固定的速率 period 执行任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)让任务固定延时 initialDelay 后以固定的速率 period 执行任务

Executors 线程池工厂

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

基于 ThreadPoolExecutor 的构造方法我们可以知道:

  • 核心线程数 == 最大线程数,即没有救急线程,故也不需要设置超时时间
  • LinkedBlockingQueue 是阻塞队列的 链表 实现,因此可以认为阻塞队列是无界的,可以放任意数量的任务

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

基于 ThreadPoolExecutor 的构造方法我们可以知道:

  • 核心线程数是 0,最大线程数是 Integer.MAX_VALUE,即线程池创建出来的都是救急线程,而救急线程的空闲生存时间是 60s,意味着救急线程可以无限创建
  • SynchronousQueue 实现特点是:它没有容量,它的 put 和 take 都是阻塞方法,类似于 管道

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

由 FinalizableDelegatedExecutorService 可知,在方法里包装了真正的线程池,也就是我们无法直接修改核心线程数、最大线程数等参数

具体使用场景:

希望多个任务排队顺序执行,线程数固定为 1,任务数多于 1 时,会放入无界队列排序;任务执行完毕,这唯一的线程也不会被释放。

既然 singleThreadExecutor 是单线程,那为什么不使用我们自己的单线程呢?
  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任务补救措施,但是线程池会新建一个线程保证池的正常执行

  • Executors.newSingleThreadExecutor() 线程个数始终为 1,不能修改

    FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法

  • Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改正确处理执行任务异常

    对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

RejectedExecutionHandle 拒绝策略

附上 JDK 的源码:

package java.util.concurrent;

/**
 * A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}.
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

JDK 提供了四种默认的拒绝策略实现:

请添加图片描述

public class ThreadPoolExectuor {
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { } 
 		/**
         * 让调用者自己去执行本任务
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
    public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        /**
         * 默认策略,抛出 RejectedExecutionException 异常
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
    public static class DiscardPolicy implements RejectedExecutionHandler {
        public DiscardPolicy() { }
        /**
         * 空实现,即放弃本次任务
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() { }
        /**
         * 放弃阻塞队列中最早的任务,并用本任务代之
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
}

总结

  1. AbortPolicy:默认策略,抛出 RejectedExecutionException 异常
  2. CallerRunsPolicy:让调用让调用者自己去执行本任务
  3. DiscardOldestPolicy:放弃阻塞队列中最早的任务,并用本任务代之
  4. DiscardPolicy:空实现,即放弃本次任务

BlockingQueue 阻塞队列

请添加图片描述

常用的阻塞队列:

  • ArrayBlockingQueue:基于数组的阻塞队列
  • LinkedBlockingQueue:基于链表的阻塞队列
  • SynchronousQueue:没有容量,它的 put 和 take 都是阻塞方法,类似于 **管道
  • DelayQueue:延时队列

线程池异常处理

测试

如果不处理异常,那么这个业务异常将会被吞噬,这会给我们的业务带来难以估计的后果

package jucexception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

public class Test {
    public static final Logger LOG = Logger.getLogger("Test");
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool.submit(() -> {
            LOG.info("start");
            int i = 1 / 0;
            LOG.info("end");
        });
    }
}

请添加图片描述

可见,发生了异常线程结束了,但是没有异常信息打印出来,并且业务逻辑也没有正常执行!

方法一:调用者主动捕获异常

package jucexception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

public class Test {
    public static final Logger LOG = Logger.getLogger("Test");
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool.submit(() -> {
            LOG.info("start");
            try {
                int i = 1 / 0;
            } catch (Exception e) {
                e.printStackTrace();
            }
            LOG.info("end");
        });
    }
}

请添加图片描述

方法二:基于 Future

package jucexception;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Logger;

public class Test {
    public static final Logger LOG = Logger.getLogger("Test");
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        Future<Boolean> future = pool.submit(() -> {
            LOG.info("start");
            int i = 1 / 0;
            LOG.info("end");
            return true;
        });
        System.out.println(future.get());
    }
}

请添加图片描述

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值