线程池详解、ThreadPoolExecutor、异步模式之工作线程、任务调度线程池、 Fork/Join

1.线程池简介

线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。如数据库连接池。使用线程池的优点如下:

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

在这里插入图片描述

2.自定义线程池

步骤一:自定义任务阻塞队列
步骤二:自定义线程池
步骤三:自定义拒绝策略
步骤四:测试

/**
 * 自定义线程池
 */

@Slf4j
public class TestPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, new RejectPolicy<Runnable>() {
            @Override
            public void reject(BlockingQueue<Runnable> queue, Runnable task) {
                // 拒绝策略
                // 1、死等
                // queue.put(task);

                // 2、带超时等待
                queue.offer(task, 2000, TimeUnit.MILLISECONDS);

                // 3、让调用者放弃任务执行
                // log.debug("放弃-{}", task);

                // 4、让调用者抛弃异常--后续任务都不执行
                // throw new RuntimeException("任务执行失败" + task);

                // 5、让调用者自己执行任务
                // task.run();
            }
        });
        // 创建5个任务
        for (int i = 0; i < 4; i++) {
            int j = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug("{}", j);
                }
            });
        }
    }
}

/**
 * 拒绝策略接口
 * @param <T>
 */
@FunctionalInterface
interface RejectPolicy<T> {
    void reject(BlockingQueue<T> queue, T task);
}


/**
 * 自定义线程池
 */
@Slf4j
class ThreadPool {

    // 阻塞任务队列
    private final BlockingQueue<Runnable> taskQueue;

    // 线程集合
    private final HashSet<Worker> workers = new HashSet<>();

    // 核心线程数
    private final int coreSize;

    // 获取任务的超时时间(超过超时时间还没任务,结束这个线程)
    private final long timeout;
    // 时间单位
    private final TimeUnit timeUnit;

    // 拒绝策略
    private final RejectPolicy<Runnable> rejectPolicy;

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapacity);
        this.rejectPolicy = rejectPolicy;
    }

    // 执行任务
    public void execute(Runnable task) {
        synchronized (workers) {
            // 当任务数没有超过 coreSize 时,直接交给 worker 对象执行
            // 如果任务数超过 coreSize 时,加入任务队列暂存
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.debug("新增 worker {}, {}", worker, task);
                workers.add(worker);
                worker.start();
            } else {
                // taskQueue.put(task); // 这一种死等
                // 拒绝策略
                // 1、死等
                // 2、带超时等待
                // 3、让调用者放弃任务执行
                // 4、让调用者抛弃异常
                // 5、让调用者自己执行任务
                taskQueue.tryPut(rejectPolicy, task);

            }
        }
    }

    class Worker extends Thread {
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            // 执行任务
            // 1): 当task不为空, 执行任务
            // 2): 当task执行完毕, 从阻塞队列中获取任务并执行
            // while (task != null || (task = taskQueue.take()) != null) {
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                try {
                    log.debug("正在执行...{}", task);
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    task = null;
                }
            }

            // 将线程集合中的线程移除
            synchronized (workers) {
                log.debug("worker被移除 {}", this);
                workers.remove(this);
            }
        }
    }

}

/**
 * 用于存放任务的-阻塞队列
 *
 * @param <T> Runnable, 任务抽象为Runnable
 */
@Slf4j
class BlockingQueue<T> {

    // 1、任务队列
    private final Deque<T> queue = new ArrayDeque<>();

    // 2、锁
    private final ReentrantLock lock = new ReentrantLock();

    // 3、生产者的条件变量 (当阻塞队列塞满任务的时候, 没有空间, 此时进入条件变量中等待)
    private final Condition fullWaitSet = lock.newCondition();

    // 4、消费者的条件变量 (当没有任务可以消费的时候, 进入条件变量中等待)
    private final Condition emptyWaitSet = lock.newCondition();

    // 5、阻塞队列的容量
    private final int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }


    // 从阻塞队列中获取任务, 如果没有任务, 会等待指定的时间
    public T poll(long timeout, TimeUnit unit) {
        lock.lock();
        try {
            // 将timeout统一转换为纳秒
            long nanos = unit.toNanos(timeout);
            while (queue.isEmpty()) {
                try {
                    // 表示超时, 无需等待, 直接返回null
                    if (nanos <= 0) {
                        return null;
                    }
                    // 返回值的时间(剩余时间) = 等待时间 - 经过时间 所以不存在虚假唤醒(时间还没等够就被唤醒,然后又从新等待相同时间)
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal(); // 唤醒生产者进行生产, 此时阻塞队列没有满
            return t;
        } finally {
            lock.unlock();
        }
    }

    // 从阻塞队列中获取任务, 如果没有任务,会一直等待
    public T take() {
        lock.lock();
        try {
            // 阻塞队列是否为空
            while (queue.isEmpty()) {
                // 进入消费者的条件变量中等待,此时没有任务供消费
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 阻塞队列不为空, 获取队列头部任务
            T t = queue.removeFirst();
            fullWaitSet.signal(); // 唤醒生产者进行生产, 此时阻塞队列没有满
            return t;
        } finally {
            lock.unlock();
        }
    }

    // 往阻塞队列中添加任务
    public void put(T task) {
        lock.lock();
        try {
            // 阻塞队列是否满了
            while (queue.size() == capacity) {
                try {
                    log.debug("等待进入阻塞队列...");
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 添加到队列尾部
            queue.addLast(task);
            log.debug("加入任务阻塞队列 {}", task);
            emptyWaitSet.signal(); // 此时阻塞队列中有任务了, 唤醒消费者进行消费任务
        } finally {
            lock.unlock();
        }
    }

    // 往阻塞队列中添加任务(带超时)
    public boolean offer(T task, long timeout, TimeUnit timeUnit) {
        lock.lock();
        try {
            long nanos = timeUnit.toNanos(timeout);
            while (queue.size() == capacity) {
                try {
                    if (nanos <= 0) {
                        return false;
                    }
                    log.debug("等待进入阻塞队列 {}...", task);
                    nanos = fullWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("加入任务阻塞队列 {}", task);
            queue.addLast(task);
            emptyWaitSet.signal(); // 此时阻塞队列中有任务了, 唤醒消费者进行消费任务
            return true;
        } finally {
            lock.unlock();
        }
    }

    // 获取队列大小
    public int size() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }

    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否满
            if (queue.size() == capacity) {
                rejectPolicy.reject(this, task);
            } else {
                // 有空闲
                log.debug("加入任务队列 {}", task);
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        } finally {
            lock.unlock();
        }
    }
}

3.ThreadPoolExecutor

3.1 线程池状态

ThreadPoolExecutor使用int的高3位来表示线程池状态,低29 位表示线程数量
在这里插入图片描述
从数字上比较,TERMINATED> TIDYING>STOP> SHUTDOWN> RUNNING

这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以用一次cas原子 操作进行赋值

// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));

// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }

3.2 构造方法

   /**
     *使用给定的初始参数创建新的ThreadPoolExecutor。
     *
     *corePoolSize – 池中要保留的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
     				意思最少要保留的线程数 哪怕没有任务执行 也不会被销毁
     				
     * maximumPoolSize – 池中允许的最大线程数
     * 
     * keepAliveTime – 当线程数大于核心时,这时多余空闲线程在终止前等待新任务的最长时间。
     * 		如corePoolSize =5 maximumPoolSize= 10 
     * 		当前线程数为8 此时 3个线程无任务 当时间超过keepAliveTime多余3个就会被销毁
     * 
     * unit–keepAliveTime参数的时间单位
     * 
     * @param workQueue 工作队列在任务执行之前用于保留任务的队列。
     * 					此队列将只保存由execute方法提交的可运行runnable任务。
     * 
     * threadFactory – 执行器创建新线程时使用的工厂
     * 
     * handler–当执行因达到线程边界和队列容量而被阻止时使用的处理程序(拒绝策略) 
     * 					意思就是当任务大于线程执行数 多余的任务该如何处理

     */
  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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize 核心线程数(最多保留的线程数)
线程池当中所一直维护的线程数量,如果线程池处于任务空闲期间,那么该线程也不会被回收掉(注意如果线程池数量小于corePoolSize值 执行多个任务会先创建线程执行任务超过该值才会进行复用 设值讲究 一般是cpu核心数X2附近)

maximumPoolSize 最大线程数目
线程池中所维护的线程数的最大数量

maximumPoolSize - corePoolSize = 救急线程数
在这里插入图片描述

keepAliveTime 生存时间-针对救急线程
超过了corePoolSize的线程在经过keepAliveTime时间后如果一直处于空闲状态, 那么超过的这部分线程将会被回收掉

unit–keepAliveTime参数的时间单位
keepAliveTime参数的时间单位

workQueue阻塞队列

  • 有界阻塞队列 ArrayBlockingQueue
  • 无界阻塞队列 LinkedBlockingQueue
  • 最多只有一个同步元素的 SynchronousQueue
  • 优先队列 PriorityBlockingQueue

threadFactory 线程工厂-可以为线程创建时起个好名字

handler拒绝策略

3.3 工作方式

在这里插入图片描述
在这里插入图片描述

3.4 拒绝策略

拒绝执行处理器(拒绝策略) 表示当前线程池中的线程都在忙于执行任务且阻塞队列(有界队列)也已经满了的情况下,新到来的任务该如被对待和处理 ,JDK提供了四种拒绝策略

在这里插入图片描述
RejectedExecutionHandler 拒绝执行处理程序(策略根接口)
一般JDK提供的策略不会满足我们日常开发,里面对应方法rejectedExecution 具体策略方法实现我们根据自己的实际业务情况进行重写

/**
 * A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}.
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface RejectedExecutionHandler {

    /**
	   方法,当execute无法接受任务时,ThreadPoolExecutor可能会调用该方法。
	   当线程或队列槽因超出其界限而不再可用时,或者在执行器shutdown关闭时,可能会发生这种情况。
     *
     * @param r–请求执行的可运行任务
     * @param executor–尝试执行此任务的执行者(线程池对象)
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

1.AbortPolicy(终止策略)默认策略
这是一个处理器针对被拒绝的任务 直接抛出RejectedExecutionException异常

    /**
     *这是一个处理器针对被拒绝的任务 直接抛出RejectedExecutionException异常
     * 
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * 构造方法
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         * 总是抛出RejectedExecutionException
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

2.DiscardPolicy(丢弃策略)
我们可以看到rejectedExecution执行拒绝的策略为空实现 什么都不做,也不抛出异常


    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

3.DiscardOldestPolicy(丢弃旧任务策略)
一种被拒绝任务的处理程序,它丢弃(workQueue队列)最老的未被处理请求(队列最先被放进去的任务),然后调用e.execute(r);重试执行当前任务(注意依然要走流程),除非执行器关闭,在这种情况下任务被丢弃。


    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
	       获取并忽略执行器将执行的下一个任务(如果有一个任务立即可用),
	       然后重试执行任务r,除非执行器关闭,在这种情况下,任务r被丢弃。
         *@param r请求执行的可运行任务
         *@param e试图执行此任务的执行者(线程池)
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {//线程池是否被关闭
            	//弹出WorkQueue队列第一个元素 
                e.getQueue().poll();
                //将任务插到队列队尾 依然要走完整流程
                e.execute(r);
            }
        }
    }

4.CallerRunsPolicy(调用方运行策略)
被拒绝任务的处理程序,它直接由提交任务的线程来运行这个提交的任务,除非executor已关闭,在这种情况下任务被丢弃。

 /**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
            	//直接执行
                r.run();
            }
        }
    }

3.5 Executors

Executors 工厂类 提供多种方法创建各种类型线程池(最好手动创建线程更灵活的使用线程池参数 Executors将其隐藏 实际业务我们需要根据并发量来调节线程池参数 如默认采用拒绝策略都是AbortPolicy 直接抛出异常)

代码举例

public class MyTest1 {

    public static void main(String[] args) {
        /*
         Executors 工厂类 提供多种方法创建各种类型线程池
         (最好手动创建线程更灵活的使用线程池参数 Executors将其隐藏 实际业务我们需要根据并发量来调节线程池参数 如默认采用拒绝策略都是AbortPolicy 直接抛出异常)

         如: newFixedThreadPool(3) 固定大小为3的线程池
            newSingleThreadExecutor 单个线程池
            newCachedThreadPool 缓冲线程池 pool会根据任务数自动分配线程执行任务 具体线程数根据任务决定

         本质上底层都是实例化ThreadPoolExecutor 根据不同的线程池 传入不同的对象
         */
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        ExecutorService executorService1 = Executors.newSingleThreadExecutor();
        ExecutorService executorService2 = Executors.newCachedThreadPool();

    }
}

3.5.1 newFixedThreadPool

创建一个固定大小的线程池

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

特点

  • 核心线程数==最大线程数(没有救急线程被创建),因此也无需超时时间
  • 阻塞队列是无界的,可以放任意数量的任务
  • 适用于任务量已知,相对耗时的任务

代码举例

@Slf4j
public class TestFixedThreadPool {
   public static void main(String[] args) {
      // 自定义线程工厂
      ThreadFactory factory = new ThreadFactory() {
         final AtomicInteger atomicInteger = new AtomicInteger(0);

         @Override
         public Thread newThread(Runnable r) {
            return new Thread(r, "myPool_T" + atomicInteger.getAndIncrement());
         }
      };

      // 创建核心线程数量为2的线程池
      // 通过 ThreadFactory可以给线程添加名字

      ExecutorService executorService = Executors.newFixedThreadPool(2, factory);

      // 任务
      Runnable runnable = new Runnable() {
         @Override
         public void run() {
            log.debug("hello word!"); // [myPool_T0] - hello word!
         }
      };
      
      executorService.execute(runnable);
   }
}

3.5.2 newCachedThreadPool

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

特点

  • 核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s,意味着

  • 全部都是救急线程(60s后可以回收)

  • 救急线程可以无限创建

  • 队列采用了 SynchronousQueue实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手 交货)

  • 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线 程。

  • 适合任务数比较密集,但每个任务执行时间较短的情况

3.5.3 newSingleThreadExecutor

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

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

区别

  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建 一个线程,保证池的正常工作
  • Executors.newSingleThreadExecutor()线程个数始终为1,不能修改
    FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了ExecutorService接口,因此不 能调用 ThreadPoolExecutor 中特有的方法
  • Executors.newFixedThreadPool(1)初始时为1,以后还可以修改
    对外暴露的是 ThreadPoolExecutor对象,可以强转后调用 setCorePoolSize等方法进行修改

3.6 提交任务

// 执行任务
void execute(Runnable command);

// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
 
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

execute源码

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    // 获取ctl
    int c = ctl.get();
    
    // 判断当前启用的线程数是否小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
        // 为该任务分配线程
        if (addWorker(command, true))
            // 分配成功就返回
            return;
        
        // 分配失败再次获取ctl
        c = ctl.get();
    }
    
    // 分配和信息线程失败以后
    // 如果池状态为RUNNING并且插入到任务队列成功
    if (isRunning(c) && workQueue.offer(command)) {
        
        // 双重检测,可能在添加后线程池状态变为了非RUNNING
        int recheck = ctl.get();
        
        // 如果池状态为非RUNNING,则不会执行新来的任务
        // 将该任务从阻塞队列中移除
        if (! isRunning(recheck) && remove(command))
            // 调用拒绝策略,拒绝该任务的执行
            reject(command);
        
        // 如果没有正在运行的线程
        else if (workerCountOf(recheck) == 0)
            // 就创建新线程来执行该任务
            addWorker(null, false);
    }
    
    // 如果添加失败了(任务队列已满),就调用拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}

其中调用了 addWoker() 方法,再看看看这个方法

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        // 如果池状态为非RUNNING状态、线程池为SHUTDOWN且该任务为空 或者阻塞队列中已经有任务
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            // 创建新线程失败
            return false;

        for (;;) {
            // 获得当前工作线程数
            int wc = workerCountOf(c);

            // 参数中 core 为true
            // CAPACITY 为 1 << COUNT_BITS-1,一般不会超过
            // 如果工作线程数大于了核心线程数,则创建失败
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 通过CAS操作改变c的值
            if (compareAndIncrementWorkerCount(c))
                // 更改成功就跳出多重循环,且不再运行循环
                break retry;
            // 更改失败,重新获取ctl的值
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                // 跳出多重循环,且重新进入循环
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    // 用于标记work中的任务是否成功执行
    boolean workerStarted = false;
    // 用于标记worker是否成功加入了线程池中
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 创建新线程来执行任务
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            // 加锁
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                // 加锁的同时再次检测
                // 避免在释放锁之前调用了shut down
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 将线程添加到线程池中
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    // 添加成功标志位变为true
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 如果worker成功加入了线程池,就执行其中的任务
            if (workerAdded) {
                t.start();
                // 启动成功
                workerStarted = true;
            }
        }
    } finally {
        // 如果执行失败
        if (! workerStarted)
            // 调用添加失败的函数
            addWorkerFailed(w);
    }
    return workerStarted;
}

submit举例

public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService pool = Executors.newFixedThreadPool(3);

        Future<String> future = pool.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "hello word";
            }
        });

        System.out.println(future.get()); // hello word

    }

invokeAll举例

@Slf4j
public class TestInvokeAll {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService pool = Executors.newFixedThreadPool(2);

        List<Future<String>> futures = pool.invokeAll(Arrays.asList(
                () -> {
                    log.debug("begin");
                    Thread.sleep(1000);
                    return "1";
                },
                () -> {
                    log.debug("begin");
                    Thread.sleep(500);
                    return "2";
                },
                () -> {
                    log.debug("begin");
                    Thread.sleep(2000);
                    return "3";
                }
        ));

        for (Future<String> future : futures) {
            log.debug(future.get());
        }
    }

}
2022-03-17 18:19:36 [pool-1-thread-1] - begin
2022-03-17 18:19:36 [pool-1-thread-2] - begin
2022-03-17 18:19:36 [pool-1-thread-2] - begin
2022-03-17 18:19:38 [main] - 1
2022-03-17 18:19:38 [main] - 2
2022-03-17 18:19:38 [main] - 3

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

3.7 关闭线程池

shutdown()

// 线程池状态变为 SHUTDOWN
// 不会接收新任务
// 但已提交的任务会执行完
// 此方法不会阻塞调用线程的执行(shutdown() 后的代码可以继续执行)
void shutdown();
/**
* 将线程池的状态改为 SHUTDOWN
* 不再接受新任务,但是会将阻塞队列中的任务执行完
*/
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        
        // 修改线程池状态为 SHUTDOWN
        advanceRunState(SHUTDOWN);
        
  		// 中断空闲线程(没有执行任务的线程)
        // Idle:空闲的
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 尝试终结,不一定成功
    // 
    tryTerminate();
}

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 终结失败的条件
        // 线程池状态为RUNNING
        // 线程池状态为 RUNNING SHUTDOWN STOP (状态值大于TIDYING)
        // 线程池状态为SHUTDOWN,但阻塞队列中还有任务等待执行
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        
        // 如果活跃线程数不为0
        if (workerCountOf(c) != 0) { // Eligible to terminate
            // 中断空闲线程
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 处于可以终结的状态
            // 通过CAS将线程池状态改为TIDYING
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    // 通过CAS将线程池状态改为TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

shutdownNow()

/**
* 将线程池的状态改为 STOP
* 不再接受新任务,也不会在执行阻塞队列中的任务
* 会将阻塞队列中未执行的任务返回给调用者
* 并用 interrupt 的方式中断正在执行的任务
*/
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        
        // 修改状态为STOP,不执行任何任务
        advanceRunState(STOP);
        
        // 中断所有线程
        interruptWorkers();
        
        // 将未执行的任务从队列中移除,然后返回给调用者
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 尝试终结,一定会成功,因为阻塞队列为空了
    tryTerminate();
    return tasks;
}

其它方法

// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();

// 线程池状态是否是 TERMINATED
boolean isTerminated();

// 调用 shutdown 后,由于调用使线程结束线程的方法是异步的并不会等待所有任务运行结束就返回,因此如果它想在线程池 TERMINATED 后做些其它事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

4.异步模式之工作线程

4.1 定义

让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型 实现就是线程池,也体现了经典设计模式中的享元模式。

例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务 员,那么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message)

注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率

例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不咋 地,分成服务员(线程池A)与厨师(线程池B)更为合理,当然你能想到更细致的分工

4.2 饥饿

固定大小线程池会有饥饿现象

  • 两个工人是同一个线程池中的两个线程
  • 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
    • 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
    • 后厨做菜:没啥说的,做就是了
  • 比如工人А处理了点餐任务,接下来它要等着工人B把菜做好,然后上菜,他俩也配合的蛮好
  • 但现在同时来了两个客人,这个时候工人A和工人B都去处理点餐了,这时没人做饭了,饥饿

现象

@Slf4j
public class TestStarvation {

    static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");

    static Random RANDOM = new Random();
    static String cooking() {
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(2);


        pool.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = pool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        pool.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = pool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

    }
}

在这里插入图片描述

4.3 饥饿-解决

不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率

@Slf4j
public class TestStarvation {

    static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");

    static Random RANDOM = new Random();
    static String cooking() {
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }

    public static void main(String[] args) {
        // 服务员线程池
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);

        // 厨师线程池
        ExecutorService cookPool = Executors.newFixedThreadPool(1);

        waiterPool.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        waiterPool.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

    }
}
2022-03-17 20:37:41 [pool-1-thread-1] - 处理点餐...
2022-03-17 20:37:41 [pool-2-thread-1] - 做菜
2022-03-17 20:37:41 [pool-1-thread-1] - 上菜: 辣子鸡丁
2022-03-17 20:37:41 [pool-1-thread-1] - 处理点餐...
2022-03-17 20:37:41 [pool-2-thread-1] - 做菜
2022-03-17 20:37:41 [pool-1-thread-1] - 上菜: 地三鲜

总结起来就是如果一个线程要等待另一个线程的结果或者满足的条件,这两个线程就不适合在一个线程池里面

4.4 创建多少线程池合适

过小会导致程序不能充分地利用系统资源、容易导致饥饿
过大会导致更多的线程上下文切换,占用更多内存

4.4.1 CPU 密集型运算

通常采用 cpu核数+1能够实现最优的 CPU利用率,+1是保证当线程由于页缺失故障(操作系统)或其它 原因导致暂停时,额外的这个线程就能顶上去,保证 CPU时钟周期不被浪费

4.4.2 I/O密集型运算

在这里插入图片描述

5.任务调度线程池

在『任务调度线程池』 功能加入之前,可以使用java.util.Timer 来实现定时功能,Timer 的优点在于简单易 用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在 执行,前一个任务的延迟或异常都将会影响到之后的任务。

5.1 Timer(已过时)

@Slf4j
public class TestTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();

        TimerTask task1 = new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                log.debug("task 1");
                Thread.sleep(1000);
            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task 2");
            }
        };

		// 使用timer添加两个任务, 希望他们都在1s后执行
		// 由于timer内只有一个线程来执行队列中的任务, 所以task2必须等待task1执行完成才能执行
        timer.schedule(task1, 1000);
        timer.schedule(task2, 1000);


    }
}
2022-03-17 21:13:43 [Timer-0] - task 1
2022-03-17 21:13:44 [Timer-0] - task 2

5.2 ScheduledThreadPoolExecutor

延时执行任务

@Slf4j
public class ScheduledExecutorServiceTest {

    public static void main(String[] args) {

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        log.debug("start...");
        executor.schedule(() -> {
            log.debug("task1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 2, TimeUnit.SECONDS);

        executor.schedule(() -> {

            log.debug("task2");

        }, 2, TimeUnit.SECONDS);

    }

}
2022-03-17 21:38:28 [main] - start...
2022-03-17 21:38:30 [pool-1-thread-2] - task2
2022-03-17 21:38:30 [pool-1-thread-1] - task1

定时执行-scheduleAtFixedRate方法的使用
每隔1秒执行一次
真正的时间间隔会取任务执行时长与设置的时间间隔的最大值

@Slf4j
public class ScheduledExecutorServiceTest {

    public static void main(String[] args) {

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        log.debug("start...");

        executor.scheduleAtFixedRate(() -> {

            log.debug("task2");

        }, 2, 1, TimeUnit.SECONDS);

    }

}
2022-03-17 21:45:59 [main] - start...
2022-03-17 21:46:01 [pool-1-thread-1] - task2
2022-03-17 21:46:02 [pool-1-thread-1] - task2
2022-03-17 21:46:03 [pool-1-thread-1] - task2
2022-03-17 21:46:04 [pool-1-thread-1] - task2
2022-03-17 21:46:05 [pool-1-thread-1] - task2
.
.
.

scheduleWithFixedDelay-每个任务之间的间隔时间
睡眠时间 + 速率时间, 为打印的间隔时间

@Slf4j
public class ScheduledExecutorServiceTest {

    public static void main(String[] args) {

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        log.debug("start...");

        executor.scheduleWithFixedDelay(() -> {

            log.debug("task2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, 2, 1, TimeUnit.SECONDS);

    }

}
2022-03-17 21:58:52 [main] - start...
2022-03-17 21:58:54 [pool-1-thread-1] - task2
2022-03-17 21:58:56 [pool-1-thread-1] - task2
2022-03-17 21:58:58 [pool-1-thread-1] - task2
2022-03-17 21:59:00 [pool-1-thread-1] - task2
.
.
.

5.3 正确处理线程池异常

@Slf4j
public class ScheduledExecutorServiceTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        log.debug("start...");

        ScheduledFuture<Boolean> future = executor.schedule(() -> {

            log.debug("task2");
            // 自己 try catch 处理
//            try {
//                int i = 1 / 0;
//            } catch (Exception e) {
//                log.error(e.getMessage());
//            }

            int ii = 1 / 0;

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // future 如果有异常 会返回异常信息
            return true;
        }, 2, TimeUnit.SECONDS);

        log.debug("result{}",future.get());

    }

}
2022-03-17 22:13:14 [main] - start...
2022-03-17 22:13:16 [pool-1-thread-1] - task2
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.concurrent.threadpool.ScheduledExecutorServiceTest.main(ScheduledExecutorServiceTest.java:50)
Caused by: java.lang.ArithmeticException: / by zero
	at com.concurrent.threadpool.ScheduledExecutorServiceTest.lambda$main$0(ScheduledExecutorServiceTest.java:38)

5.4 应用-定时任务

如何让每周四 18:00:00 定时执行任务

public class TestSchedule {

    // 如何让每周四 18:00:00 定时执行任务?
    public static void main(String[] args) {
        //  获取当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        // 获取周四时间
        LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        // 如果 当前时间 > 本周周四,必须找到下周周四
        if(now.compareTo(time) > 0) {
            time = time.plusWeeks(1);
        }
        System.out.println(time);
        // initailDelay 代表当前时间和周四的时间差
        // period 一周的间隔时间
        long initailDelay = Duration.between(now, time).toMillis();
        long period = 1000 * 60 * 60 * 24 * 7;
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.scheduleAtFixedRate(() -> {
            System.out.println("running...");
        }, initailDelay, period, TimeUnit.MILLISECONDS);
    }
}

6.Fork/Join

概念

Fork/Join是JDK1.7加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的cpu密 集型运算

所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的 一些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解

Fork/Join在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升 了运算效率

ForkJoin默认会创建与cpu核心数大小相同的线程池

使用
提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值)

求 1~n 之 间整数的和

@Slf4j
public class ForkJoinTest {

    public static void main(String[] args) {

        ForkJoinPool forkJoinPool = new ForkJoinPool(4);

        Integer invoke = forkJoinPool.invoke(new SumTask(5));

        // new MyTask(5)  5+ new MyTask(4)  4 + new MyTask(3)  3 + new MyTask(2)  2 + new MyTask(1)

        log.debug(invoke.toString()); // 15

    }

}

/**
 * 1~n 之间整数和
 */
@Slf4j
class SumTask extends RecursiveTask<Integer> {

    private static final long serialVersionUID = -6233297242909774841L;

    private final int n;

    public SumTask(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "{" + n + '}';
    }

    @Override
    protected Integer compute() {

        // 终止条件
        if (n == 1) {
            log.debug("join() {}", n);
            return 1;
        }

        SumTask t1 = new SumTask(n - 1);

        t1.fork(); // 让一个线程去执行此任务
        log.debug("fork() {} + {}", n, t1);

        Integer result = n +  t1.join(); // 获取任务结果
        log.debug("join() {} + {} = {}", n, t1, result);

        return result;
    }
}
2022-03-17 23:22:13 [ForkJoinPool-1-worker-1] - fork() 5 + {4}
2022-03-17 23:22:13 [ForkJoinPool-1-worker-0] - fork() 2 + {1}
2022-03-17 23:22:13 [ForkJoinPool-1-worker-3] - fork() 3 + {2}
2022-03-17 23:22:13 [ForkJoinPool-1-worker-0] - join() 1
2022-03-17 23:22:13 [ForkJoinPool-1-worker-2] - fork() 4 + {3}
2022-03-17 23:22:13 [ForkJoinPool-1-worker-0] - join() 2 + {1} = 3
2022-03-17 23:22:13 [ForkJoinPool-1-worker-3] - join() 3 + {2} = 6
2022-03-17 23:22:13 [ForkJoinPool-1-worker-2] - join() 4 + {3} = 10
2022-03-17 23:22:13 [ForkJoinPool-1-worker-1] - join() 5 + {4} = 15
2022-03-17 23:22:13 [main] - 15

Process finished with exit code 0

在这里插入图片描述

改进

@Slf4j
public class ForkJoinTest {

    public static void main(String[] args) {

        ForkJoinPool forkJoinPool = new ForkJoinPool(4);
        
        // new MyTask(5)  5+ new MyTask(4)  4 + new MyTask(3)  3 + new MyTask(2)  2 + new MyTask(1)
        Integer invoke = forkJoinPool.invoke(new AddTask(1, 5));
        log.debug(invoke.toString()); // 15

    }

}



/**
 * 1~n 之间整数和 改进
 */
@Slf4j
class AddTask extends RecursiveTask<Integer> {

    int begin;
    int end;

    public AddTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    public String toString() {
        return "AddTask{" +
                "begin=" + begin +
                ", end=" + end +
                '}';
    }

    @Override
    protected Integer compute() {

        if (begin == end) {
            log.debug("join() {}", begin);
            return begin;
        }

        if (end - begin == 1) {
            log.debug("join() {} + {} = {}", end ,begin ,end + begin);
            return end + begin;
        }

        // 1 5
        int mid = (begin + end) / 2; // 3

        AddTask t1 = new AddTask(1, mid);// 1~3
        t1.fork();

        AddTask t2 = new AddTask(mid + 1,end);// 4~5
        t2.fork();

        log.debug("fork() {} + {} = ?", t1, t2);

        int result = t1.join() + t2.join();
        log.debug("join() {} + {} = {}", t1, t2, result);

        return result;
    }
}
2022-03-17 23:40:52 [ForkJoinPool-1-worker-1] - fork() AddTask{begin=1, end=3} + AddTask{begin=4, end=5} = ?
2022-03-17 23:40:52 [ForkJoinPool-1-worker-0] - join() 2 + 1 = 3
2022-03-17 23:40:52 [ForkJoinPool-1-worker-3] - join() 5 + 4 = 9
2022-03-17 23:40:52 [ForkJoinPool-1-worker-2] - fork() AddTask{begin=1, end=2} + AddTask{begin=3, end=3} = ?
2022-03-17 23:40:52 [ForkJoinPool-1-worker-1] - join() 3
2022-03-17 23:40:52 [ForkJoinPool-1-worker-2] - join() AddTask{begin=1, end=2} + AddTask{begin=3, end=3} = 6
2022-03-17 23:40:52 [ForkJoinPool-1-worker-1] - join() AddTask{begin=1, end=3} + AddTask{begin=4, end=5} = 15
2022-03-17 23:40:52 [main] - 15

Process finished with exit code 0

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值