Java:103-并发编程详解(下篇)

这里还是续写上一章博客
线程池与Future:
线程池的实现原理:
下图所示为线程池的实现原理:调用方不断地向线程池中提交任务,线程池中有一组线程,不断地从队列中取 任务,这是一个典型的生产者—消费者模型

在这里插入图片描述

要实现这样一个线程池,有⼏个问题需要考虑:
1:队列设置多长?,如果是无界的,调用方不断地往队列中放任务,可能导致内存耗尽,如果是有界的,当队 列满了之后,调用方如何处理?
2:线程池中的线程个数是固定的,还是动态变化的?
3:每次提交新任务,是放入队列?还是开新线程?
4:当没有任务的时候,线程是睡眠一小段时间?还是进入阻塞?如果进入阻塞,如何唤醒?
这里给出一个案例:
package main6;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *
 */
public class ThreadPoolTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 1.创建一个线程池
        //static ExecutorService newFixedThreadPool(int nThreads),创建一个可重用固定线程数的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // 2.向线程池中布置任务
        //Runnable Future<V> submit(Callable<T> task),执行任务和命令,通常用于执行
        Future<?> submit = executorService.submit(new a()); //可以选择不获得返回值
        System.out.println(submit+"66");
        Object o = submit.get();
        System.out.println(o+"11");
        Future submit1 = executorService.submit(new aa()); 
        System.out.println(submit1+"77");
        Object oo = submit1.get(); //这里会等待,上面的不会,基本没有返回值,即返回的是null,而这里不是,说明不是implements Callable的不进行操作返回值
        System.out.println(oo+"33");
        // 3.关闭线程池
        executorService.shutdown();
        new a().start();
    }

    public static class a extends Thread {
        @Override
        public void run() {
            System.out.println(currentThread());
            System.out.println(1);
        }
    }

    public static class aa implements Callable {
        @Override
        public Object call() throws Exception {
            int sum = 0;
            for (int i = 1; i <= 10000; i++) {
                sum += i;
            }
            System.out.println("计算的累加和是:" + sum);
            return sum;
        }
    }
}


//很明显,我们当前调用线程是main,提交的任务是一个可以自己执行的类(自己操作start),只是这个执行start由线程池来操作,或者说由其他线程来执行他(不是自己执行start,看上面的System.out.println(currentThread());打印结果就知道了),所以实际上线程池只是帮你执行对应的run方法,但不是你自己执行,当然这并不是优点(因为也要代码,且差别不大,但是与数据库连接池不同,连接池由于对应的连接若具体创建的话,代码会比较多,所以对于连接池来说这里是优点),最主要的是他可以帮我们进行维护(这里主要对线程池线程(处理线程)来说的说明,而不是调用线程,因为线程池里面的线程就是处理线程),比如他可以重复利用(相当于自旋中,有任务执行完,继续自旋,只是有些线程可能会有过期时间自动不自旋使得清除该线程了,而核心线程没有,后面会大致说明的),而不用继续创建线程

//即前面的队列可以认为就是调用线程,然后由处理线程来执行

针对问题4,有3种做法:
1:不使用阻塞队列,只使用一般的线程安全的队列,也无阻塞/唤醒机制,当队列为空时,线程池中的线程 只能睡眠一会⼉,然后醒来去看队列中有没有新任务到来,如此不断轮询
2:不使用阻塞队列,但在队列外部、线程池内部实现了阻塞/唤醒机制(比如当有任务时提醒你去执行),只是没有阻塞队列而已,只是在外面进行总体的阻塞,但他们还是执行的,相当于手动的阻塞调用线程或者阻塞处理线程,比较麻烦,因为要手动
3:使用阻塞队列,没有就阻塞,有就释放
很显然,做法3最完善,既避免了做法2中线程池内部自己实现阻塞/唤醒机制的麻烦,也避免了做法1的睡眠/轮询带来 的资源消耗和延迟,正因为如此,所以接下来要讲的ThreadPoolExector/ScheduledThreadPoolExecutor都是基于阻 塞队列来实现的,而不是一般的队列,⾄此,各式各样的阻塞队列就要派上用场了
一般来说,队列任务是有顺序的给的,只是线程来抢而已,而这样就会考虑是否加上锁(或者CAS)
线程池的类继承体系:
线程池的类继承体系如下图所示:

在这里插入图片描述

还是一样的,实线白色箭头是继承,虚线白色箭头是实现,注意,可能并不是直接的继承或者实现,比如:
public class ThreadPoolExecutor extends AbstractExecutorService {
public abstract class AbstractExecutorService implements ExecutorService {
那么ThreadPoolExecutor间接的实现ExecutorService,这里要注意(可能是版本不同出现不同的代码,即修改了)
在这里,有两个核⼼的类: ThreadPoolExector 和 ScheduledThreadPoolExecutor ,后者不仅是可以执行某 个任务,最重要的是还可以周期性地执行任务
向线程池中提交的每个任务,都必须实现 Runnable 接口,通过最上面的 Executor 接口中的execute(Runnable command) 向线程池提交任务
然后,在 ExecutorService 中,定义了线程池的关闭接口 shutdown() ,还定义了可以有返回值的任务, 也就是 Callable ,后面会详细介绍(前面案例也给出了具体实现)
ThreadPoolExecutor:
核⼼数据结构:
基于线程池的实现原理,下面看一下ThreadPoolExector的核⼼数据结构
public class ThreadPoolExecutor extends AbstractExecutorService {
 //...
 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 // 存放任务的阻塞队列
 private final BlockingQueue<Runnable> workQueue;
 // 对线程池内部各种变量进行互斥访问控制
 private final ReentrantLock mainLock = new ReentrantLock();
 // 线程集合
 private final HashSet<Worker> workers = new HashSet<Worker>();
 //...

}
每一个线程是一个Worker对象,Worker是ThreadPoolExector的内部类,核⼼数据结构如下:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
 // ...

 final Thread thread; // Worker封装的线程

 Runnable firstTask; // Worker接收到的第1个任务

 volatile long completedTasks; // Worker执行完毕的任务个数

 // ...

}
由定义会发现,Worker继承于AQS,也就是说Worker本身就是一把锁(既然是AQS,那么通常是利用CAS来实现的锁),这把锁有什么用处呢?用于线程池的 关闭、线程执行任务的过程(中的操作)
核⼼配置参数解释:
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;
    }
上面的各个参数,解释如下:
1:corePoolSize:在线程池中始终维护的线程个数
2:maximumPoolSize:在corePooSize已满、队列也满的情况下,扩充(扩容)线程⾄此值(一般可以动态修改线程池的参数的,不只是包括这个,大多数具体参数的,一般都会提供动态修改,具体可以百度)(但是始终维护的还是corePoolSize,只是线程数量扩充而已,虽然他会执行,但是在没有执行后超过的会进行回收, 当然,这个回收一般是超过的,比如以corePooSize为5为例,有1,2,3,4,5,6,这个6是超过的,那么可以回收,无论后面加上7还是8,前面的1,2,3,4,5不会变,通常是这样,当然,如果是从头加入,那么就是6,1,2,3,4,即会变的,一般是尾部加入),一般线程数量不超过他,实际上在服务器中,为了满足用户的数量,通常会设置当前cpu处理的最大数量(具体可以百度)
3:keepAliveTime/TimeUnit:maxPoolSize 中的空闲线程销毁所需要的时间,总线程数收缩回corePoolSize,而TimeUnit代表时间单位,比如TimeUnit.SECONDS就代表秒(前面也操作过这个构造方法)
4:blockingQueue:线程池所用的队列类型
5:threadFactory:线程创建工⼚,可以自定义,有默认值 Executors.defaultThreadFactory()
6:RejectedExecutionHandler:corePoolSize已满,队列已满,maxPoolSize 已满,最后的拒绝策略
下面来看这6个配置参数(第三个说明了两个)在任务的提交过程中是怎么运作的,在每次往线程池中提交任务的时候,有如下的处 理流程:
步骤一:判断当前线程数是否大于或等于corePoolSize,如果小于,则新建线程执行,如果大于等于(一般是等于,大于是防止出现问题),则进入步骤 二
步骤二:判断队列是否已满,如未满,则放入,如已满,则进入步骤三
步骤三:判断当前线程数是否大于或等于maximumPoolSize,如果小于,则继续新建线程执行,如果大于或者等于(一般是等于,大于是防止出现问题),则进入步骤 四
步骤四:根据拒绝策略,开始拒绝任务(因为到这里,那么步骤二基本是满足,且线程也到顶了,那么我是忙不过来了,还是拒绝任务吧)
总结一下:首先判断corePoolSize,其次判断blockingQueue是否已满,接着判断maxPoolSize(maximumPoolSize的简称),最后使用拒 绝策略
很显然,基于这种流程,如果队列是无界的,将永远没有机会走到步骤三(因为步骤二中永远不会都满足条件),也即maximumPoolSize没有使用,也一 定不会走到步骤四
当然,还要考虑异常的处理会不会导致线程池的线程销毁,通常是会的(有些操作不会,比如ThreadPoolExecutor的submit,因为里面的execute操作异常后返回的是submit的而不是他execute的异常信息),然后考虑是否操作新线程,如果没有到最小,自然会创建,否则通常不会,还需要看线程池的实现(比如可能一些其他的框架,如tomcat是不同与jdk的线程池实现),当然,一般都是销毁看最小
线程池的优雅关闭:
线程池的关闭,较之线程的关闭更加复杂,当关闭一个线程池的时候,有的线程还正在执行某个任务,有的调 用者正在向线程池提交任务,并且队列中可能还有未执行的任务,因此,关闭过程不可能是瞬时的,而是需要一个 平滑的过渡(即优雅的关闭),这就涉及线程池的完整⽣命周期管理
1:线程池的生命周期
在JDK 7中,把线程数量(workerCount)和线程池状态(runState)这两个变量打包存储在一个字段里面, 即ctl变量(也就是前面说明的private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));),如下图所示,最高的3位存储线程池状态,其余29位存储线程个数,而在JDK 6中,这两个变量是分开存储的

在这里插入图片描述

由上面的代码可以看到,ctl变量被拆成两半,最高的3位用来表示线程池的状态(最终的是COUNT_MASK的结果),低的29位表示线程的个数, 线程池的状态有五种,分别是RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED
下面分析状态之间的迁移过程,如图所示:

在这里插入图片描述

线程池有两个关闭方法,shutdown()和shutdownNow(),这两个方法会让线程池切换到不同的状态,在队列 为空,线程池也为空之后,进入TIDYING 状态,最后执行一个钩子方法terminated(),进入TERMINATED状态,线 程池才真正关闭
这里的状态迁移有一个⾮常关键的特征:从小到大迁移,-1,0,1(0可以到1,也可以直接到2,看图就知道了),2,3,只会从小的状态值往大的状态值迁 移,不会逆向迁移,例如,当线程池的状态在TIDYING=2时,接下来只可能迁移到TERMINATED=3,不可能迁移回STOP=1或者其他状态
除 terminated()之外,线程池还提供了其他⼏个钩子方法,这些方法的实现基本都是空的,如果想实现自己的线程 池,可以重写这⼏个方法:
protected void beforeExecute(Thread t, Runnable r) { }

protected void afterExecute(Runnable r, Throwable t) { }

protected void terminated() { }
2:正确关闭线程池的步骤(上面只是说明状态,而不是关闭操作)
关闭线程池的过程为:在调用 shutdown()或者shutdownNow()之后,线程池并不会立即关闭,我们也可以调 用 awaitTermination() 来等待或者判断线程池是否完全关闭,那么可以任务关闭线程池的正确步骤如下:
// executor.shutdownNow();

executor.shutdown();

try {
 boolean flag = true;
 do {
 flag = ! executor.awaitTermination(500, TimeUnit.MILLISECONDS); //参数2:代表毫秒
     //只要返回true代表关闭了,那么由于!即是false,则执行后续代码,也就使得可以完成要让线程池完全关闭了,才可以执行后续代码的需求
 } while (flag);
} catch (InterruptedException e) {
 // ...

}
awaitTermination(…)方法的内部实现很简单,如下所示,不断循环判断线程池是否到达了最终状态TERMINATED,如果是,就返回,如果不是,则通过termination条件变量阻塞一段时间,之后继续判断(因为始终的运行是不好的)
public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //判断线程池状态,是否是terminated(TERMINATED),如果是则不循环,返回true
            while (runStateLessThan(ctl.get(), TERMINATED)) {
                if (nanos <= 0L)
                    return false; //这里代表时间,如果在对应的时间内还没有是对应的状态,那么继续循环重新等待该时间(若是对应的状态,基本会返回true),所以有时候如果出现等待时间不对的情况,就是这里的问题(大多数的等待时间出现不对的情况或者其他类操作,基本是这样的情况的)
                nanos = termination.awaitNanos(nanos); //进行阻塞等待,等待对应的时间(也可被唤醒),在线程多的情况下,我们一般会操作阻塞,而不是自旋,既然可以被唤醒,那么为什么要操作等待时间,这是为了保证不会始终的等待或者说不依靠对应的唤醒,总要有其他的路吧,因为他未必操作唤醒(虽然基本都会,因为修改状态后立即唤醒,在后面的tryTerminate方法里面可以了解)
            }
            return true;
        } finally {
            mainLock.unlock();
        }
    }

3:shutdown()与shutdownNow()的区别
shutdown()不会清空任务队列,会等所有任务执行完成(既然提交了任务,我就一定要完成,这里一般在对应中断操作里进行),shutdownNow()清空任务队列(直接清空任务),当然他会先中断线程,然后清空,保证(有)任务没有被操作
shutdown()只会中断空闲的线程,shutdownNow()会中断所有线程,中断自然也会使得任务认为执行完了
public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); //加锁,确保线程安全,防止多个线程多次操作出现问题,特别是方法体操作和状态,而有这个,就不需要再方法体里考虑多个线程了,只需要考虑一个线程就可,而状态之所以会考虑是因为后面的方法若也操作可能会导致状态修改不对,使得STOP可能是这个地方的操作,当然,这里自然是加上的
        try {
            checkShutdownAccess(); //检查是否有关闭线程池的权限
            advanceRunState(SHUTDOWN); //将线程池状态修改为SHUTDOWN
            interruptIdleWorkers(); //中断空闲的线程
            //具体对应方法体的钩子方法(一般是空方法体)
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

 public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); //加锁,确保线程安全
        try {
            checkShutdownAccess(); //检查是否有关闭线程池的权限
            advanceRunState(STOP); //将线程池状态修改为STOP
            interruptWorkers(); //中断所有的线程
            tasks = drainQueue(); //任务队列清空
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
下面看一下在上面的代码里中断空闲线程和中断所有线程的区别
shutdown()方法中的interruptIdleWorkers()方法的实现:
 private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }

 private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //如果tryLock成功,表示线程处于空闲状态,如果不成功,表示线程持有锁,正在执行某个任务
                if (!t.isInterrupted() && w.tryLock()) { //!t.isInterrupted()保证他是没有中断的,这是一个前提,因为你可以外部进行手动中断(前面的中断测试就是这样),虽然基本不能得到该线程使得这样操作,若得到了自然可以
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
关键区别点在tryLock():一个线程在执行一个任务之前,会先加锁,这意味着通过是否持有锁,可以判断出线 程是否处于空闲状态,tryLock()如果调用成功,说明线程处于空闲状态,向其发送中断信号(true),否则不发送
 public boolean tryLock()  { return tryAcquire(1); }

 protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true; //到这里说明是空闲的,所以对于前面的对应的&&来说是true了
            }
            return false;
        }
shutdownNow()调用了 interruptWorkers()方法:
 private void interruptWorkers() {
        // assert mainLock.isHeldByCurrentThread();
        for (Worker w : workers)
            w.interruptIfStarted();
    }

   void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    //只要启动了,并且没有被中断,则一律中断(前面可是for循环),这里可没有对应类似的tryLock操作
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
在上面的代码中,shutdown() 和shutdownNow()最终都调用了tryTerminate()方法,如下所示:
final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateLessThan(c, STOP) && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            // 当workQueue为空,wordCount为0时,执行下述代码
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 将状态切换到TIDYING状态
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated(); // 调用钩子函数
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0)); // 将状态由TIDYING改为TERMINATED
                        termination.signalAll(); // 通知awaitTermination(...),可以不用等待了
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

tryTerminate()不会强行终⽌线程池,只是做了一下检测:当workerCount为0,workerQueue为空时,先把 状态切换到TIDYING,然后调用钩子方法terminated(),当钩子方法执行完成时,把状态从TIDYING 改为 TERMINATED,接着调用termination.sinaglAll(),通知前面阻塞在awaitTermination的所有调用者线程使得返回(前面说明了awaitTermination)
所以,TIDYING和TREMINATED的区别是在二者之间执行了一个钩子方法terminated(),⽬前是一个空实现
任务的提交过程分析:
提交任务的方法如下:
public void execute(Runnable command) { //一般前面的submit最终会执行到这个
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
    // 如果当前线程数小于corePoolSize,则启动新线程
        if (workerCountOf(c) < corePoolSize) {
            // 添加Worker,并将command设置为Worker线程的第一个任务并开始执⾏
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    // 如果当前的线程数大于或等于corePoolSize,则调⽤workQueue.offer放入队列(注意队列是需要自己操作的,这里就不说明了,在后面的延迟队列中就可以了解,或者在对应的后面的案例里也可以了解)
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 如果线程池正在停⽌,则将command任务从队列移除,并拒绝command任务请求
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 放入队列中后发现没有线程执⾏任务,开启新线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
    // 线程数大于maxPoolSize,并且队列已满(满了不会添加队列,所以是else if到这里),调⽤拒绝策略
        else if (!addWorker(command, false))
            reject(command); //很明显一般拒绝策略也就是拒绝任务请求,一般拒绝是防止他继续进入(后面会说明该策略的)
    }


// 该⽅法⽤于启动新线程,如果第二个参数为true,则使⽤corePoolSize作为上限,否则使⽤maxPoolSize作为上限
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (int c = ctl.get();;) {
            // 如果线程池状态值若起码是SHUTDOWN和STOP,或者第一个任务不是null(满足是前面的状态的情况下),或者⼯作队列为空
            // 则添加worker失败,返回false
            // Check if queue empty only if necessary.
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP)
                    || firstTask != null
                    || workQueue.isEmpty()))
                return false;

            for (;;) {
                // ⼯作线程数达到上限,要么是corePoolSize要么是maximumPoolSize,启动线程失败
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                // 增加worker数量成功,返回到retry语句
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                // 如果线程池运⾏状态起码是SHUTDOWN,则重试retry标签语句,CAS
                if (runStateAtLeast(c, SHUTDOWN))
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

    // worker数量加1成功后,接着运⾏
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 新建worker对象
            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.
                    int c = ctl.get();

                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        // 由于线程已经在运⾏中,⽆法启动,抛异常
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 将线程对应的worker加入worker集合
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    // 释放锁
                    mainLock.unlock();
                }
                // 如果添加worker成功,则启动该worker对应的线程
                if (workerAdded) {
                    t.start(); //这一般是新的线程来操作的(看对应的构造方法就知道了),注意里面利用了ThreadFactory来使得根据你传递的线程创建一个新的,而不是你传递的线程来执行的,来防止你在外面手动执行自身的start使得报错(不会执行报错后面的代码),所以若你在外面运行,若加上了该运行的线程作为参数传递,并不会报错,而是多一个线程运行(可以自己测试),这是因为线程池是新的,所以可以多次提交一样的,因为是不同的线程来执行的
                    workerStarted = true;
                }
            }
        } finally {
            // 如果启动新线程失败
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
任务的执行过程分析:
在上面的任务提交过程中,可能会开启一个新的Worker(然后执行,前面的start,最终关键操作如下:run方法),并把任务本身作为firstTask赋给该Worker,但对于 一个Worker来说,不是只执行一个任务,而是源源不断地从队列中取任务执行,这是一个不断循环的过程(如执行完变成空,继续赋值)
下面来看Woker的run()方法的实现过程:
 private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
     
     // 当前Worker对象封装的线程
     final Thread thread;
     
     // 线程需要运⾏的第一个任务,可以是null,如果是null,则线程从队列获取任务
     Runnable firstTask;
     // 记录线程执⾏完成的任务数量,每个线程一个计数器
     volatile long completedTasks;
     
     /**
     * 使⽤给定的第一个任务并利⽤线程⼯⼚创建Worker实例
     * @param firstTask 线程的第一个任务,如果没有,就设置为null,此时线程会从队列获取任务
 */
      Worker(Runnable firstTask) {
          // 线程处于阻塞状态,调⽤runWorker的时候中断
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
     
     // 调⽤ThreadPoolExecutor的runWorker⽅法执⾏线程的运⾏
      public void run() {
            runWorker(this);
        }
     
      final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
          // 中断(释放锁,就算没有加锁也可以这样,这是保证可以操作后续的操作的)Worker封装的线程
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 如果线程初始任务不是null,或者从队列获取的任务不是null,表示该线程应该(在)执⾏任务
            while (task != null || (task = getTask()) != null) { //getTask()会可以是阻塞的,若是空闲的则会阻塞,因为没有任务,当然,他里面会循环的获取任务
                // 获取线程锁
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                // 如果线程池停⽌了,确保线程被中断
                // 如果线程池正在运⾏,确保线程不被中断
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    // 获取到任务后,再次检查线程池状态,如果发现线程池已经停⽌,则给自己发中断信号(做个保险,虽然我们手动的停止也可,即shutdown或者shutdownNow的相关操作,或者手动中断,注意:由于线程池的线程是内部的,所以我们通常手动的中断不了,因为基本得不到该线程,若得到了自然可以)
                    wt.interrupt();
                try {
                    // 任务执⾏之前的钩子⽅法,实现为空
                    beforeExecute(wt, task);
                    try {
                        task.run(); //其中Runnable task = w.firstTask;,即很明显,他最终还是操作了我们传递的任务的run方法的执行,在前面我们说过他的线程执行者是不同的,这是因为在中途可能修改了名称或者修改了唯一标识,使得认为是不同了,所以外面也可以操作start而不会报错
                        // 任务执⾏结束后的钩子⽅法,实现为空
                        afterExecute(task, null);
                    } catch (Throwable ex) {
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
                    // 任务执⾏完成,将task设置为null
                    task = null;
                    // 线程已完成的任务数加1
                    w.completedTasks++;
                    // 释放线程锁
                    w.unlock();
                }
            }
            // 判断线程是否是正常退出
            completedAbruptly = false;
        } finally {
            // Worker退出
            processWorkerExit(w, completedAbruptly);
        }
    }

     
 }
shutdown()与任务执行过程综合分析:
把任务的执行过程和上面的线程池的关闭过程结合起来进行分析,当调用 shutdown()的时候,可能出现以下 ⼏种场景:
1:当调用shutdown()的时候,若所有线程都处于空闲状态,这意味着任务队列一定是空的,此时,所有线程都会阻塞在 getTask()方法的地方(通常使得不会执行对应的循环,一般空闲会在这里),然后,所有线程都会 收到interruptIdleWorkers()发来的中断信号,getTask()返回null(中断就不阻塞了,因为有处理了中断操作),所有Worker都会退出while循环,之 后执行processWorkerExit(线程也退出或者删除,不是核心的)
2:当调用shutdown()的时候,若所有线程都处于忙碌状态,此时,队列可能是空的,也可能是⾮空的,interruptIdleWorkers()内部的tryLock调用失败,什么都不会 做(调用成功就会发送中断信号,而调用失败说明在执行中,不是空闲的,前面也说明了),所有线程会继续执行自己当前的任务,之后所有线程会执行完队列中的任务,直到队列为空,getTask()才会返回null(他也会分配队列任务),所以之后,就和场景1一样了,最终也是退出while循环
3:当调用shutdown()的时候,部分线程忙碌,部分线程空闲,有部分线程空闲,说明队列一定是空的,这些线程肯定阻塞在 getTask()方法的地方,空闲的这些线程会 和场景1一样处理,不空闲的线程会和场景2一样处理
下面看一下getTask()方法的内部细节:
 private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();

            // Check if queue empty only if necessary.
            // 如果线程池调⽤了shutdownNow(),返回null(前面没有说明这种情况就是因为直接返回null,即实际上始终相当于前面的场景1,又因为他是先中断,且该中断无论是否空闲都这样,而不会保留忙碌的)
            // 如果线程池调⽤了shutdown(),并且任务队列为空(如果任务队列满,说明没有空闲的,自然不会返回null了,减少判断或者后面代码的操作),也返回null(又因为对应可能不操作中断,即tryLock调⽤失败,即忙碌不会中断,而不是中断无论是否空闲都这样,即会保留忙碌的)
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
                // ⼯作线程数减一
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 如果队列为空(因为是多线程,所以可以为空的情况),就会阻塞pool或者take,前者有超时时间,后者没有超时时间,这里就是阻塞的地方,并且也是得到任务的地方,一般得到任务就会解除
                // 一旦中断,此处抛异常,但是处理了异常,继续执行(但是不得任务,因为中断,且我们还是没有忙碌的),然后对应上⽂场景1
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
shutdownNow() 与任务执行过程综合分析:
和上面的 shutdown()类似,只是多了一个环节,即清空任务队列,如果一个线程正在执行某个业务代码,即 使向它发送中断信号,也没有用,只能等它把代码执行完成(从上面看中断会继续执行),因此,中断空闲线程和中断所有线程的区别并不是很 大(除⾮线程当前刚好阻塞在某个地方,所以要这样说,中断所有空闲线程和中断所有线程的区别不大),所以实际上shutdownNow() 主要的操作就是清空所有队列,所以使得始终相当于场景1(因为无论是否忙碌都中断了,相当于场景1的所有空闲都中断)
当一个Worker最终退出的时候,会执行清理工作:
 private void processWorkerExit(Worker w, boolean completedAbruptly) {
     // 如果线程正常退出,不会执⾏if的语句,这里一般是⾮正常退出,需要将worker数量减一
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            // 将自己的worker从集合移除(也就是线程集合,即处理线程的集合,一般会保留核心数)
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

     // 每个线程在结束的时候都会调⽤该⽅法,看是否可以停⽌线程池
        tryTerminate();

        int c = ctl.get();
     // 如果在线程退出前,发现线程池还没有关闭
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // 如果线程池中没有其他线程了,并且任务队列⾮空
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                // 如果⼯作线程数大于min,表示队列中的任务可以由其他线程执⾏,退出当前线程
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            // 如果当前线程退出前发现线程池没有结束,任务队列不是空的,也没有其他线程来执⾏
            // 就再启动一个线程来处理,保证线程池没有关闭前的情况的处理,即也继续运行(当然一个线程执行完任务通常会消失(或者说销毁,在我们操作Thread时也是这样)的,所以需要再启动一个线程,按照这样的理解,可以说核心线程是在销毁之前始终有任务的,虽然这个任务什么也没有做,保证他不销毁,而这个任务也就是始终拿取队列任务,完成,继续循环(或者阻塞,即执行该不是队列的任务)),这就是为什么线程会自动的去队列拿取任务的原因(前面也有拿取任务的后续,即总体来说任务执行完,还是需要拿取任务,但这里是在线程池快要关闭的情况,而不是执行完去队列拿的情况,或者说没有任务而继续拿的情况(执行完相当于没有任务,对应是null也是这样的,所以null也是启动线程执行,即addWorker(null, true);(后面会出现)或者addWorker(null, false);(前面已经给出过了)))
            addWorker(null, false);
        }
    }
线程池的4种拒绝策略:
在前面说明execute(Runnable command)的最后,调用了reject(command)执行拒绝策略,代码如下所示
else if (!addWorker(command, false))
            reject(command);
   final void reject(Runnable command) {
        handler.rejectedExecution(command, this); //第二个是this
    }

//在前面的构造方法中有this.handler = handler;

//private volatile RejectedExecutionHandler handler;
handler就是我们可以设置的拒绝策略管理器:
RejectedExecutionHandler 是一个接口,定义了四种实现,分别对应四种不同的拒绝策略,默认是AbortPolicy
package java.util.concurrent;

public interface RejectedExecutionHandler {
 void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
ThreadPoolExecutor类中默认的实现是:
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

 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;
    }

//即默认的RejectedExecutionHandler handler的确是defaultHandler
//默认的ThreadFactory threadFactory是Executors.defaultThreadFactory()

//对应默认的defaultHandler是如下结果:
 private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

四种策略的实现代码如下(对应ThreadPoolExecutor的四个内部类):
策略1:调用者直接在自己的线程里执行,线程池不处理,比如到医院打点滴,医院没地方了,到你家自己操 作吧:
 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(); //自己执行自己
            }
        }
    }
策略2:线程池抛异常(这个是默认的):
public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws 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());
        }
    }
策略3:线程池直接丢掉任务,神不知⻤不觉(什么都不处理,自然就是丢掉了,因为也没有在队列里面(因为是操作满的)):
  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) {
        }
    }
策略4:删除队列中最早的任务,将当前任务入队列:
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead 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()) {
                e.getQueue().poll();
                e.execute(r); //继续放入任务来操作,即继续执行execute方法(因为这个e一般是this,即ThreadPoolExecutor类对象,前面也说明(给出)了:handler.rejectedExecution(command, this); //第二个是this)
            }
        }
    }
示例程序:
package main6;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 *
 */
public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 1,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
                new ThreadPoolExecutor.DiscardPolicy()); //使用策略3,即不做任何操作
        for (int i = 0; i < 20; i++) {
            int finalI = i;
            //如果是submit那么可以操作返回值(当然对应里面的submit也是最终执行了execute的,你可以看源码就知道了)
            /*
            AbstractExecutorService类的:
 public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

             */

            executor.execute(new Runnable() {
                @Override
                public void run() {
                    //这个id并不是一定从0开始的,要注意
                    System.out.println(Thread.currentThread().getId() + "[" + finalI + "] -- 开始");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getId() + "[" + finalI + "] -- 结束");
                }
            });
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(1);
        executor.shutdown();
        boolean flag = true;
        try {
            do {
                flag = !executor.awaitTermination(1, TimeUnit.SECONDS);
                System.out.println(flag);
            } while (flag);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("线程池关闭成功。。。");
        System.out.println(Thread.currentThread().getId());

        //根据结果可以看到,的确少了很多的线程(finalI值没有很大),即的确没有对应的线程了,即策略操作成功
        //现在你可以将策略new ThreadPoolExecutor.DiscardPolicy()修改成new ThreadPoolExecutor.CallerRunsPolicy()来试试
        //可以发现,他们都进行了操作,基本没有少线程(finalI很大,如果出现没有到最大,基本是在没有操作之前进行了关闭线程池了),但这里是循环后的,所以基本都先进行了任务加入
        //如果放在中间,由于代码先后顺序,必然导致他的关闭需要执行完他的逻辑,自然不会操作后续的线程了
    }
}

Executors工具类:
concurrent包提供了Executors工具类,利用它可以创建各种不同类型的线程池,前面有操作过,如:
ExecutorService executorService = Executors.newFixedThreadPool(10);

//虽然上面的案例是ThreadPoolExecutor,最终实现ExecutorService的,即也可以用ExecutorService的引用(因为父类指向子类)
四种对比:
单线程的线程池:
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
固定数⽬线程的线程池:
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
每接收一个请求,就创建一个线程来执行(也需要考虑空闲时间):
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
单线程具有周期调度功能的线程池:
 public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
多线程,有调度功能的线程池:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
有ThreadPool的是可以多,有Executor的是单
最佳实践:
不同类型的线程池,其实都是由前面的⼏个关键配置参数配置而成的,在《阿里巴巴Java开发⼿册》中,明确禁⽌使用Executors创建线程池,并要求开发者直接使用ThreadPoolExector或ScheduledThreadPoolExecutor进行创建,这样做是为了强制开发者明确线程池的运行策略,使其对线程池的每个配置参数皆做到⼼中有数,以规避因使用不当而造成资源耗尽的⻛险,之所以这样,是因为我们可以看到上面的最终代码内容实际上基本(之所以是基本,实际上是因为有些是被当成参数而已,虽然基本都是Executor的子类)就是使用这两个类来实现的,而由于他们的参数是固定的,所以在一定的情况下并不是非常好的(因为固定),所以才会建议自己进行创建,而不是使用这些固定的参数
ScheduledThreadPoolExecutor:
ScheduledThreadPoolExecutor实现了按时间调度来执行任务:
1:延迟执行任务
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        RunnableScheduledFuture<Void> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit),
                                          sequencer.getAndIncrement()));
        delayedExecute(t);
        return t;
    }

  public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit) {
        if (callable == null || unit == null)
            throw new NullPointerException();
        RunnableScheduledFuture<V> t = decorateTask(callable,
            new ScheduledFutureTask<V>(callable,
                                       triggerTime(delay, unit),
                                       sequencer.getAndIncrement()));
        delayedExecute(t);
        return t;
    }
2:周期执行任务
 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0L)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period),
                                          sequencer.getAndIncrement());
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }


 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (delay <= 0L)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          -unit.toNanos(delay),
                                          sequencer.getAndIncrement());
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }
区别如下:
AtFixedRate:按固定频率执行,与任务本身执行时间无关,但有个前提条件,任务执行时间必须小于间隔时 间,例如间隔时间是5s,每5s执行一次任务,任务的执行时间必须小于5s(否则可能抢占结束执行或者重新操作间隔),那么这个时候虽然你在执行,但是不会等你执行完了,我累积的任务会去执行了
WithFixedDelay:按固定间隔执行,与任务本身执行时间有关,例如,任务本身执行时间是10s,间隔2s(delay参数),则 下一次开始执行的时间就是12s
对于延迟来说,就是等会执行(那么很明显,不是延迟的,自然是一开始就会执行的),意思容易理解,就不多说了(对应的参数只是操作是否有返回的线程执行),他们都是操作线程,而后面的间隔只操作(不是是否)没有返回(返回值)的线程执行(Runnable)
延迟执行和周期性执行的原理:
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,这意味着其内部的数据结构和ThreadPoolExecutor是基本一样的,那它是如何实现延迟执行任务和周期性执行任务的呢?
延迟执行任务依靠的是DelayQueue(延时队列,他里面的是DelayedWorkQueue,DelayQueue只是代表名称而已哦),DelayQueue是 BlockingQueue的一种,其实现原理是二叉堆,而周期性执行任务是执行完一个任务之后,再把该任务扔回到任务队列中,如此就可以对一个任务反复执行,不过这里并没有使用DelayQueue,而是在ScheduledThreadPoolExecutor内部又实现了一个特定的DelayQueue(延迟队列,前面也说明了,对应利用优先队列,由于会扩容,那么是无界的,没有容量的队列也能称为无界,比如链表的实现的队列SynchronousQueue),特定的即是DelayedWorkQueue
 static class DelayedWorkQueue extends AbstractQueue<Runnable> //ScheduledThreadPoolExecutor的内部类DelayedWorkQueue
        implements BlockingQueue<Runnable> {
其原理和DelayQueue一样,但这里操作了任务,所以在队列基础上一般会进行优化,比如针对任务的取消进行了优化,下面主要讲延迟执行和周期性执行的实现过程
延迟执行:
 public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
     //父类指向子类
        RunnableScheduledFuture<Void> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit),
                                          sequencer.getAndIncrement()));
        delayedExecute(t);
        return t;
    }

/*
private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {
            */


/*

*/
传进去的是一个Runnable,外加延迟时间delay,在内部通过decorateTask(…)方法把Runnable包装成一个ScheduleFutureTask对象,而DelayedWorkQueue中存放的正是这种类型的对象,这种类型的对象一定实现了Delayed接口:
//DelayedWorkQueue里面的 
private RunnableScheduledFuture<?>[] queue =
            new RunnableScheduledFuture<?>[INITIAL_CAPACITY];

//public interface RunnableScheduledFuture<V> extends RunnableFuture<V>, ScheduledFuture<V> {
//public interface ScheduledFuture<V> extends Delayed, Future<V> { //最终实现了Delayed接口
我们继续看看上面的delayedExecute(t);方法内容:
 protected <V> RunnableScheduledFuture<V> decorateTask(
        Runnable runnable, RunnableScheduledFuture<V> task) {
        return task; //直接返回第二个参数的值,所以就是返回ScheduledFutureTask对象
    }  

private void delayedExecute(RunnableScheduledFuture<?> task) {
        if (isShutdown())
            reject(task);
        else {
            super.getQueue().add(task); //将ScheduledFutureTask放入队列,被使用,父类是ThreadPoolExecutor,但是队列由于被我们的ScheduledThreadPoolExecutor构造方法进行赋值了,且是new DelayedWorkQueue(),所以他对应的队列的确是我们的延迟队列
            if (!canRunInCurrentRunState(task) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

  void ensurePrestart() {
      //如果wc小于corePoolSize,则addWorker,开新线程,否则什么也不做
        int wc = workerCountOf(ctl.get());
        if (wc < corePoolSize)
            addWorker(null, true);
        else if (wc == 0)
            addWorker(null, false); //对于null来说,会到task = getTask(),也是来分配任务的,所以也算开启线程
    }

从上面的代码中可以看出,schedule()方法本身很简单,就是把提交的Runnable任务加上delay时间,转换成ScheduledFutureTask对象,放入DelayedWorkerQueue中,任务的执行过程还是复用的ThreadPoolExecutor, 延迟的控制是在DelayedWorkerQueue内部完成的(因为我们设置的线程池的队列是延迟队列)
周期性执行:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0L)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period), //正数
                                          sequencer.getAndIncrement());
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

  public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (delay <= 0L)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          -unit.toNanos(delay), //负数
                                          sequencer.getAndIncrement());
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }
和schedule(…)方法的框架基本一样,也是包装一个ScheduledFutureTask对象,只是在延迟时间参数之外多 了一个周期参数,然后放入DelayedWorkerQueue就结束了
两个方法的区别在于一个传入的周期是一个负数,另一个传入的周期是一个正数,为什么要这样做呢?
主要用于⽣成任务序列号的sequencer,创建ScheduledFutureTask的时候使用:
    private static final AtomicLong sequencer = new AtomicLong();
private class ScheduledFutureTask<V>
 extends FutureTask<V> implements RunnableScheduledFuture<V> {
 private final long sequenceNumber;
 private volatile long time;
 private final long period;
    
      ScheduledFutureTask(Runnable r, V result, long triggerTime,
                            long sequenceNumber) { //之前的延迟是这个(Runnable的)
            super(r, result);
            this.time = triggerTime; //在延时中就是对应的间隔
            this.period = 0;
            this.sequenceNumber = sequenceNumber;
        }
    
    ScheduledFutureTask(Runnable r, V result, long triggerTime,
         long period, long sequenceNumber) {
         super(r, result);
         this.time = triggerTime; // 延迟时间
         this.period = period; // 周期
         this.sequenceNumber = sequenceNumber;
 }
    
    // 实现Delayed接口
 public long getDelay(TimeUnit unit) {
 return unit.convert(time - System.nanoTime(), NANOSECONDS);
 }
    
    // 实现Comparable接口,比较谁先执行
     public int compareTo(Delayed other) {
            if (other == this) // compare zero if same object
                return 0;
            if (other instanceof ScheduledFutureTask) {
                ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
                long diff = time - x.time;
                if (diff < 0)
                    return -1;
                else if (diff > 0)
                    return 1;
                // 延迟时间相等,进一步比较序列号
                else if (sequenceNumber < x.sequenceNumber)
                    return -1;
                else
                    return 1;
            }
            long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
            return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
        }
    
    // 实现Runnable接口
     public void run() {
            if (!canRunInCurrentRunState(this))
                cancel(false);
         // 如果不是周期执行,则执行一次
            else if (!isPeriodic())
                super.run();
         // 如果是周期执行,则重新设置下一次运行的时间,重新入队列
            else if (super.runAndReset()) {
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }
    
    // 下一次执行时间
      private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p; //+=则满足周期
            else
                time = triggerTime(-p);
        }
    
}

//ScheduledThreadPoolExecutor里面的方法
 long triggerTime(long delay) {
        return System.nanoTime() +
            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }

// 放到队列中,等待下一次执行
 void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(task)) {
            super.getQueue().add(task);
            if (canRunInCurrentRunState(task) || !remove(task)) {
                ensurePrestart();
                return;
            }
        }
        task.cancel(false);
    }
withFixedDelay和atFixedRate的区别就体现在setNextRunTime里面,如果是atFixedRate,period>0,下一次开始执行时间等于上一次开始执行时间(因为是time的,虽然第一次是延时,time一开始是延迟时间,既然要周期,自然要考虑加上什么时间)+period,如果是withFixedDelay,period < 0,下一次开始执行时间等于triggerTime(-p),相当于是now+(-period),now即是现在执行完的结束时间,相当于就是加上-period(period < 0)了,并且也加上了now,所以说AtFixedRate是基本固定的频率,因为他基本不变(不考虑初始的延时,那么就是period),所以可能你在执行,其他的任务可能不会等你执行完,即也会执行(因为是延迟队列,他的出队并不是有线程就出对,也是需要延迟的,而另外一个基本会等待执行,因为等待时间加上了他的执行时间(虽然线程中是没有等待的,但是由于时间的存在,看起来是等待的,虽然我们也可以根据时间来判断谁先会执行,或者说决定队列的位置(所以前面有个sequencer.getAndIncrement()来保证若时间相同时的唯一,一般代表先提交的会先被执行,即序列号小,那么先执行),一般延迟队列是根据延迟时间排列的),而这个可以执行时间大于间隔,所以其他任务也会执行
这里再次给出之前的说明:
AtFixedRate:按固定频率执行,与任务本身执行时间无关,但有个前提条件,任务执行时间必须小于间隔时 间,例如间隔时间是5s,每5s执行一次任务,任务的执行时间必须小于5s(否则可能抢占结束执行或者重新操作间隔)
WithFixedDelay:按固定间隔执行,与任务本身执行时间有关,例如,任务本身执行时间是10s(所以now即是现在执行完的结束时间),间隔2s,则 下一次开始执行的时间就是12s,即这里是相加,基本固定12s,即固定的间隔,只是与执行时间有关
简单来说,就是AtFixedRate按照period来周期,而WithFixedDelay按照执行时间(上一次的,因为我执行完了,需要开始新的一次了)加period来周期
上面是不同的策略,可以选择使用,因为是不同的方法
CompletableFuture用法:
从JDK 8开始,在Concurrent包中提供了一个强大的异步编程工具CompletableFuture,在JDK8之前,异步编 程(即并行的,否则后面怎么会阻塞(下面案例的get方法)并解除呢)可以通过线程池和Future来实现(前面给出过案例,即ThreadPoolTest类的案例,使用了线程池的,只是是固定参数,是ThreadPoolExecutor线程池的固定参数的创建方式),但功能还不够强大
示例代码:
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 *
 */
public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = new CompletableFuture<>();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            future.complete("hello world"); //相当于提交信息,或者给出返回值的任务,注意,可以不用 new Thread来执行,删除即可,即这样也行:
            /*
        future.complete("hello world"); //相当于提交信息,或者给出返回值的任务
        System.out.println("获取结果中。。。");
        String result = future.get(); //没有得到信息之前一直阻塞
        System.out.println("获取的结果:" + result);
            */
        }).start();

        System.out.println("获取结果中。。。");
        String result = future.get(); //没有得到信息之前一直阻塞
        System.out.println("获取的结果:" + result);
    }
}


CompletableFuture实现了Future接口,所以它也具有Future的特性:调用get()方法会阻塞在那,直到结果返 回,这里唯一的特点是,他可以定义返回信息,而不是经过对应的线程得到结果,或者说他只是返回参数,底层由线程使得执行方法返回而已
而由于只是返回结果,并没有清空结果(使得为null,或者继续阻塞),所以可以说1个线程调用complete方法完成该Future,则所有阻塞在get()方法的线程都将获得返回结果,同理对应的线程池加上Future也是一样的,你可以在案例中操作多个new Thread就知道了(里面要有future.get())
runAsync与supplyAsync:
上面的例子是一个空的任务(只是一个返回值),下面尝试提交一个真的任务,然后等待结果返回
runAsync(Runnable):
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 *
 */
public class CompletableFutureDemo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> voidCompletableFuture =
                CompletableFuture.runAsync(() -> {
                    try {
                        Thread.sleep(2000);
                        System.out.println("任务执行完成");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
        // 阻塞,等待任务执行完成
        Object unused = voidCompletableFuture.get();
        System.out.println(unused); //null,很明显,他只是操作执行,并没有操作返回值(默认是null的,一般清空也是null),虽然也是任务的等待,可以认为默认返回null的任务
        System.out.println("程序运行结束");
    }
}

CompletableFuture.runAsync(…)传入的是一个Runnable接口
supplyAsync(Supplier):
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 *
 */
public class CompletableFutureDemo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                try {
                    TimeUnit.SECONDS.sleep(2); //与Thread的操作基本类似,因为底层就是操作Thread的sleep的,这里代表停两秒,自己看源码就知道了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "这是结果"; //给出返回值,与上一个案例CompletableFutureDemo2是不同的,这里有返回值
            }
        });
        String result = future.get();
        System.out.println("任务执行结果:" + result);
    }
}

runAsync和supplyAsync的区别在于,supplyAsync的任务有返回值,没有返回值的任务,提交的是Runnable,返回的是 CompletableFuture,有返回值的任务,提交的是 Supplier,返回的是CompletableFuture(即对应创建对象返回的结果都是赋值给CompletableFuture类型的引用)
Supplier和前面的Callable很相似,都是操作计算结果,前面的CompletableFutureDemo案例是直接给结果了
通过上面两个例子可以看出,在基本的用法上,CompletableFuture和Future很相似,都可以提交两类任务: 一类是无返回值的,另一类是有返回值的,其中Future无返回值在前面的案例中有体现,即ThreadPoolTest案例中不操作Callable的任务就是返回null,也就是无返回值
thenRun、thenAccept和thenApply:
对于 Future,在提交任务之后,只能调用 get()等结果返回,但对于 CompletableFuture,可以在结果上面再 加一个callback,当得到结果之后,再接着执行callback
thenRun(Runnable):
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 *
 */
public class CompletableFutureDemo4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(1);
            return "这是结果";
        }).thenRun(() -> { //这里可以继续操作
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("任务执行结束之后执行的语句"); //这个在等待之前
        });

        // 阻塞等待任务执行完成
        Object o = voidCompletableFuture.get();
        //返回null,当操作了thenRun后,就没有返回值了,或者说以他的方法为主了,也可以认为他清空了返回值
        //你删除thenRun打印就知道了,会发现有返回值了
        System.out.println(o);
        System.out.println("任务执行结束");
    }
}

该案例最后不能获取到结果,只会得到一个null
thenAccept(Consumer):
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 *
 */
public class CompletableFutureDemo5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("返回中间结果");
            return "这是中间结果";
        }).thenAccept((param) -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务执行后获得前面的中间结果:" + param); //这个参数就是前面的返回值,当然,他会清空返回,使得下面返回null
        });
        // 阻塞等待任务执行完成
        Object unused = future.get();
        System.out.println(unused);
        System.out.println("任务执行完成");
    }
}

即上述代码在thenAccept中可以获取任务的执行结果,接着进行再次的处理
thenApply(Function):
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

/**
 *
 */
public class CompletableFutureDemo6 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    //Integer指最终的返回值类型(链式的最终)
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("返回中间结果");
            return "abcdefg"; //这个返回值影响Function<String中的String的值,而他影响apply(String的String,即他们对应的,否则报错
        }).thenApply(new Function<String, Integer>() { //这个Integer影响下面的Integer的返回值
            @Override
            public Integer apply(String middle) { 
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(middle); //abcdefg
                System.out.println("获取中间结果,再次计算返回"); //获得上面的结果后,继续返回,以这个返回值给后面的get,即不清空返回值,只是覆盖返回值,当然,说成清空本质上只是对返回值的重新返回说明而已,因为链式,自然是新的返回

                return middle.length();
            }
        });

        Integer integer = future.get();
        System.out.println("最终的结果为:" + integer);
    }
}

三个例子都是在任务执行完成之后,接着执行回调,只是回调的形式不同:
1:thenRun后面跟的是一个无参数、无返回值的方法,即Runnable,所以最终的返回值是CompletableFuture类型
2:thenAccept后面跟的是一个有参数、无返回值的方法,称为Consumer,返回值也是CompletableFuture类型,顾名思义,只进不出,所以称为Consumer,前面的Supplier(中间结果),是无参 数,有返回值,只出不进,和Consumer刚好相反
3:thenApply 后面跟的是一个有参数、有返回值的方法,称为Function,返回值是CompletableFuture类型,注意:参数接收的是前一个任务,即 supplyAsync(…)这个任务的返回值,因此对应的只能用supplyAsync,不能用runAsync,因为runAsync没有返回值,不能为下一个链式方法传入参数,如果用了runAsync,那么会报错,这个报错只是针对thenApply,其他默认的参数值为null,比如thenAccept就是,而之所以只是针对thenApply,是因为我们认为他是利用参数的,若参数是null,自然不能利用(因为容易出现空指针异常),所以规定对他来说是报错,而不是默认null(虽然实际上是可以不利用,也就是说也可以为null,这可能需要看版本,具体打印是null,但是返回的对象一般并不是)
thenCompose与thenCombine:
thenCompose:
在上面的例子中,thenApply接收的是一个Function,但是这个Function的返回值是一个通常的基本数据类型 或一个对象,而不是另外一个 CompletableFuture,如果 Function 的返回值也是一个CompletableFuture,就会 出现嵌套的CompletableFuture,比如考虑下面的例子:
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 *
 */
public class CompletableFutureDemo7 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<CompletableFuture<Integer>> future =
                CompletableFuture.supplyAsync(new Supplier<String>() { //这个String影响get的返回值类型,前面是默认没有写的,即Object,所以说对应的是根据get返回值来对应,实际上是默认Object造成的
                    @Override
                    public String get() {
                        return "hello world";
                    }
                }).thenApply(new Function<String, CompletableFuture<Integer>>() {
                    @Override
                    public CompletableFuture<Integer> apply(String s) { //让返回类型是CompletableFuture
                        //对于这个来说,我们只是重新的赋值返回值,即得到了CompletableFuture的结果,注意他们是不同的对象了,自然赋值的返回值是不同的,所以需要后续的操作
                        return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                            @Override
                            public Integer get() {
                                return s.length(); //可以继续嵌套但是没有必要
                            }
                        });
                    }
                });

        CompletableFuture<Integer> future1 = future.get(); //不同的对象一般说明的是 CompletableFuture,所以这里是这样的操作
        //得到他的返回值,因为不是同一个对象了,所以这里需要得到返回值
        Integer result = future1.get();
        System.out.println(result);
    }
}
如果希望返回值是一个⾮嵌套的CompletableFuture,可以使用thenCompose:
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 *
 */
public class CompletableFutureDemo8 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                return "hello world";
            }
        }).thenCompose(new Function<String, CompletionStage<Integer>>() { //中间的对应不影响返回值的赋值
            @Override
            public CompletionStage<Integer> apply(String s) {
                //public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
                return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        return s.length(); //可以认为使用thenCompose使得返回值是同一个,即虽然是不同对象,但是返回值确互通(赋值,而不是前面不互通的创建),或者就是相同的对象,自然返回值相同,所以这个执行后,返回值就是这里的结果了
                    }
                });
            }
        });
        Integer integer = future.get();
        System.out.println(integer);
    }
}

下面是thenCompose方法的接口定义:
//CompletionStage里面的
public <U> CompletionStage<U> thenCompose
        (Function<? super T, ? extends CompletionStage<U>> fn); //没有方法体,即没有(能)执行

CompletableFuture中的实现:
 public <U> CompletableFuture<U> thenCompose(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(null, fn);
    }
    //public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
//即子类CompletableFuture实现了父类CompletionStage的thenCompose方法
//当返回值类型为父类时,重写的返回值类型可以改变为子类类型,只能降低范围,不能提高,因为你在怎么降低,父类引用还是可以指向的
从该方法的定义可以看出,它传入的参数是一个Function类型(只有一个参数),并且Function的返回值必须是CompletionStage的子类,也就是CompletableFuture类型
thenCombine:
thenCombine方法的接口定义如下,从传入的参数可以看出,它不同于thenCompose
public <U,V> CompletableFuture<V> thenCombine(
        CompletionStage<? extends U> other,
        BiFunction<? super T,? super U,? extends V> fn) {
        return biApplyStage(null, other, fn);
    }
第1个参数是一个CompletableFuture类型(可以是),第2个参数是一个方法,并且是一个BiFunction,也就是该方法有2个输入参数,1个返回值(两个参数)
从该接口的定义可以大致推测,它是要在2个 CompletableFuture 完成之后,把2个CompletableFuture的返 回值传进去,再额外做一些事情,实例如下:
package main7;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 *
 */
public class CompletableFutureDemo9 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                return "hello";
            }
        }).thenCombine(CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                return "lagou";
            }
        }), new BiFunction<String, String, Integer>() { //根据前面的说明,很明显这第一个String是第一个返回值类型,第二个是第二个返回值类型,第三个是这个返回值类型,依次对应
            @Override
            public Integer apply(String s, String s2) {
                return s.length() + s2.length();
            }
        });
        Integer result = future.get();
        System.out.println(result);
    }
}

任意个CompletableFuture的组合:
上面的thenCompose和thenCombine基本只能组合2个CompletableFuture(当然由于是返回值,你可以选择嵌套,其中由于thenCombine只看第一层,那么他基本不操作嵌套,因为不改变返回值,所以他基本不会嵌套,虽然也行,但是基本没有作用),所以这里是组合,而不是嵌套(组合代表有效的使用,而不是直接的嵌套使用),而接下来的allOf 和anyOf 可以组合 任意多个CompletableFuture,方法接口定义如下所示
 public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
        return andTree(cfs, 0, cfs.length - 1);
    }


 public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
        int n; Object r;
        if ((n = cfs.length) <= 1)
            return (n == 0)
                ? new CompletableFuture<Object>()
                : uniCopyStage(cfs[0]);
        for (CompletableFuture<?> cf : cfs)
            if ((r = cf.result) != null)
                return new CompletableFuture<Object>(encodeRelay(r));
        cfs = cfs.clone();
        CompletableFuture<Object> d = new CompletableFuture<>();
        for (CompletableFuture<?> cf : cfs)
            cf.unipush(new AnyOf(d, cf, cfs));
        // If d was completed while we were adding completions, we should
        // clean the stack of any sources that may have had completions
        // pushed on their stack after d was completed.
        if (d.result != null)
            for (int i = 0, len = cfs.length; i < len; i++)
                if (cfs[i].result != null)
                    for (i++; i < len; i++)
                        if (cfs[i].result == null)
                            cfs[i].cleanStack();
        return d;
    }
⾸先,这两个方法都是静态方法,参数是变长的CompletableFuture的集合,其次,allOf和anyOf的区别,前 者是"与",后者是"或"
allOf的返回值是CompletableFuture类型,这是因为每个传入的CompletableFuture的返回值都可能 不同,所以组合的结果是无法用某种类型来表示的,索性返回Void类型
anyOf 的含义是只要有任意一个 CompletableFuture 结束,就可以做接下来的事情,而无须像AllOf那样,等 待所有的CompletableFuture结束,但由于每个CompletableFuture的返回值类型都可能不同,任意一个,意味着无法判断是什么类型,所以anyOf的返回值是CompletableFuture< Object>类型
接下来给出案例(这里提一下,我们知道他们是异步的,如果没有阻塞的存在,那么当主程序结束时,他们会中断的,也就是说,如果你循环多个对应的操作,但是由于线程池的原因,那么有些可能没有执行,就结束了):
package main7;

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

/**
 *
 */
public class CompletableFutureDemo11 {
    private static final Random RANDOM = new Random();
    private static volatile int result = 0;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture[] futures = new CompletableFuture[10];
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
                @Override
                public void run() {
                    //这里只能打印finalI,因为使用相关内部类中使用外面(外面的方法,外部方法)的局部变量是不行的,一般是默认是加上final类型的(内部类加上),在17章博客有说明
                    //但是这里我们不能打印i,是因为有i++的存在,所以他会在编译期中进行检查的,由于会加上final所以这里打印会报错,除非你删除i++就不会报错了
                    try {
                        Thread.sleep(1000 + RANDOM.nextInt(1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    result++;
                }
            });

            futures[i] = future;
        }
        System.out.println(result);
        Thread.sleep(5000); //保证他们都进行执行完毕,而不是没有执行完,你可以选择删除就知道了
        Integer anyResult = CompletableFuture.anyOf(futures).thenApply(new Function<Object, Integer>() {
            @Override
            public Integer apply(Object o) { //o是null,即返回null给这里
                return result;
            }
        }).get();
        System.out.println(anyResult);

    }
}

//上面是anyOf,下面是allOf
package main7;

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

/**
 *
 */
public class CompletableFutureDemo12 {
    private static final Random RANDOM = new Random();
    private static volatile int result = 0;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture[] futures = new CompletableFuture[10];
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000 + RANDOM.nextInt(1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    result++;
                }
            });

            futures[i] = future;
        }
        System.out.println(result);
        for (int i = 0; i < 10; i++) {
            futures[i].get();
            System.out.println(result);
        } //由于阻塞的存在,就不用我们自己动手阻塞了

        Integer allResult = CompletableFuture.allOf(futures).thenApply(new Function<Void, Integer>() {
            @Override
            public Integer apply(Void unused) {
                return result;
            }
        }).get();
        System.out.println(allResult);
    }
}

//现在你将上面的:
/*
 for (int i = 0; i < 10; i++) {
            futures[i].get();
            System.out.println(result);
        } //由于阻塞的存在,就不用我们自己动手阻塞了
*/

//进行删除,可以发现,结果还是10,所以allOf他的确进行等待了,即等待所有的CompletableFuture 结束,可以认为是等待所有的线程结束(认为线程执行一个任务自己移除)
四种任务原型:
通过上面的例子可以总结出,提交给CompletableFuture执行的任务有四种类型:Runnable(thenRun)、Consumer(thenAccept)、Supplier(supplyAsync)、Function(thenApply )下面是这四种任务原型的对比

在这里插入图片描述

对应的提交方法代表是他对应的参数是该接口
runAsync 与 supplierAsync(supplyAsync的别名) 是 CompletableFuture 的静态方法,而 thenAccept、thenAsync、thenApplyCompletableFutre的成员方法
因为初始的时候没有CompletableFuture对象,也没有参数可传,所以提交的只能是Runnable或者Supplier, 只能是静态方法
通过静态方法⽣成CompletableFuture对象之后,便可以链式地提交其他任务了,这个时候就可以提交Runnable、Consumer、Function,且都是成员方法
CompletionStage接口:
CompletableFuture不仅实现了Future接口,还实现了CompletableStage接口
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
CompletionStage接口定义的正是前面的各种链式方法、组合方法,如下所示
package java.util.concurrent;
 

public interface CompletionStage<T> {
 // 

 public CompletionStage<Void> thenRun(Runnable action);
    
 public CompletionStage<Void> thenAccept(Consumer<? super T> action);
    
 public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
    
 public <U> CompletionStage<U> thenCompose
 (Function<? super T, ? extends CompletionStage<U>> fn);
    
 public <U,V> CompletionStage<V> thenCombine
 (CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
 // ...

}
关于CompletionStage接口,有⼏个关键点要说明:
1:所有方法的返回值都是CompletionStage类型,也就是它自己,正因为如此,才能实现如下的链式调用:future1.thenApply(…).thenApply(…).thenCompose(…).thenRun(…)
2:thenApply接收的是一个有输入参数、返回值的Function,这个Function的输入参数,必须是?Super T 类型,也就是可以赋值T或者T的父类型(在22章博客有说明),而返回值则必 须是?Extends U类型,也就是U或者U的子类型(注意:虽然在某些方面可能只是确定保留类型,他只是赋值而已,并没有参与真正的操作,就如没有指向add一样,22章博客),而U恰好是thenApply的返回值的CompletionStage对 应的类型(还是注意:U只是说明,实际上看创建的对象的操作,虽然引用会在编译期影响),所以在22章博客中一般extends不能添加,因为是编译期的,实际上如果跳过这个,只会看对象,这里就是看对象
其他方法,诸如thenCompose、thenCombine也是类似的原理
CompletableFuture内部原理:
CompletableFuture的构造:ForkJoinPool
CompletableFuture中任务的执行依靠ForkJoinPool:
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
 private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor(); //使用ForkJoinPool,一般在类里面是如下:
    /*
    private static final Executor ASYNC_POOL = USE_COMMON_POOL ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
    */
    
 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
 return asyncSupplyStage(asyncPool, supplier); //使用上面的asyncPool,到如下方法
 }
    
 static <U> CompletableFuture<U> asyncSupplyStage(Executor e, Supplier<U> f) { //到这里
 if (f == null) throw new NullPointerException();
 CompletableFuture<U> d = new CompletableFuture<U>();
 // Supplier转换为ForkJoinTask
 e.execute(new AsyncSupply<U>(d, f)); //任务提交到d(先在里面赋值),到ForkJoinTask,然后ForkJoinPool(后面的适配)
 return d;
 }
 // ...

}
通过上面的代码可以看到,asyncPool是一个static类型,supplierAsync、asyncSupplyStage也都是static方 法,Static方法会返回一个CompletableFuture类型对象,之后就可以链式调用CompletionStage里面的各个方 法(子类有对应的方法的,并且是实现方式,必然有)
任务类型的适配:
ForkJoinPool接受的任务是ForkJoinTask 类型,而我们向CompletableFuture提交的任务 是 Runnable/Supplier/Consumer/Function ,因此,肯定需要一个适配机制,把这四种类型的任务转换成ForkJoinTask,然后提交给ForkJoinPool,如下图所示:

在这里插入图片描述

为了完成这种转换,在CompletableFuture内部定义了一系列的内部类,下图是CompletableFuture的各种内 部类的继承体系

在这里插入图片描述

都是白色实线,即都是继承的意思,即虽然是这样的转换,但是最终的执行还是对应参数的对应方法的
在 supplyAsync(…)方法内部,会把一个 Supplier 转换成一个 AsyncSupply(上面的e.execute(new AsyncSupply< U>(d, f));),其中f就是Supplier,然后提交给ForkJoinPool执行,在runAsync(…)方法内部,会把一个Runnable转换成一个AsyncRun,然后提交给ForkJoinPool执行,在 thenRun/thenAccept/thenApply 内部,会分别把 Runnable/Consumer/Function 转换成UniRun/UniAccept/UniApply对象,然后提交给ForkJoinPool执行,除此之外,还有两种 CompletableFuture 组合的情况,分为"与"和"或"(前面有过说明,但他们是更多的),所以有对应的Bi和Or开头的名称的类型的Completion(最终会继承的)类型
下面的代码分别为 UniRun、UniApply、UniAccept 的定义,可以看到,其内部分别封装了Runnable、Function、Consumer
static final class UniRun<T> extends UniCompletion<T,Void> {
        Runnable fn; //这个
        UniRun(Executor executor, CompletableFuture<Void> dep,
               CompletableFuture<T> src, Runnable fn) {
            super(executor, dep, src); this.fn = fn;
            
            //..
  static final class UniApply<T,V> extends UniCompletion<T,V> {
        Function<? super T,? extends V> fn; //这个
        UniApply(Executor executor, CompletableFuture<V> dep,
                 CompletableFuture<T> src,
                 Function<? super T,? extends V> fn) {
            super(executor, dep, src); this.fn = fn;
        }
 static final class UniAccept<T> extends UniCompletion<T,Void> {
        Consumer<? super T> fn; //这个
        UniAccept(Executor executor, CompletableFuture<Void> dep,
                  CompletableFuture<T> src, Consumer<? super T> fn) {
            super(executor, dep, src); this.fn = fn;
        }
很明显构造方法中都进行赋值了,所以的确是封装的,所以他们基本都是利用内部类的,即如下图所示:

在这里插入图片描述

所以还是执行对应他们封装的方法的,只不过包装了而已,这个包装可以进行其他作用(虽然这里没有说明,一般可能与入栈有关,后面会说明,因为对应的参数是需要对应的内部类的类型的(内部类的父类类型,所以内部类可以被该参数指向,因为对于内部类来说,该参数是内部类的父类))
任务的链式执行过程分析:
下面以CompletableFuture.supplyAsync(…).thenApply(…).thenRun(…)链式代码为例,分析整个执行过程
第1步:CompletableFuture future1=CompletableFuture.supplyAsync(…)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(ASYNC_POOL, supplier);
    }

    private static final Executor ASYNC_POOL = USE_COMMON_POOL ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();


 static <U> CompletableFuture<U> asyncSupplyStage(Executor e,
                                                     Supplier<U> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<U> d = new CompletableFuture<U>();
        e.execute(new AsyncSupply<U>(d, f));
        return d;
    }
在上面的代码中,关键是构造了一个AsyncSupply对象,该对象有三个关键点:
1:它继承自ForkJoinTask,所以能够提交ForkJoinPool来执行(e.execute提交任务的代码,虽然这里没有说明底层)
2:它封装了Supplier f,即它所执行任务的具体内容,所以对应的构造方法中基本就是操作任务的(虽然最终会到前面提交到的d)
3:该任务的返回值,即CompletableFuture d,也被封装在里面
ForkJoinPool执行一个ForkJoinTask类型的任务,即AsyncSupply,该任务的输入就是Supply(Supplier f,我们给的参数),输出结果存放 在CompletableFuture中

在这里插入图片描述

第2步:CompletableFuture future2=future1.thenApply(…)
第1步的返回值,也就是上面代码中的 CompletableFuture d,紧接着调用其成员方法thenApply:
 public <U> CompletableFuture<U> thenApply(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }

 private <V> CompletableFuture<V> uniApplyStage(
        Executor e, Function<? super T,? extends V> f) {
        if (f == null) throw new NullPointerException();
        Object r;
     //如果上一个任务执行结束,立即执行该任务
        if ((r = result) != null)
            return uniApplyNow(r, e, f); //这里面最终会提交任务,即e.execute(new UniApply<T,V>(null, d, this, f));
        CompletableFuture<V> d = newIncompleteFuture();
     //如果上一个任务没有执行完,则将当前任务压入任务栈
        unipush(new UniApply<T,V>(e, d, this, f));
        return d;
    }
我们知道,必须等第1步的任务执行完毕,第2步的任务才可以执行,因此,这里提交的任务不可能立即执行, 在此处构建了一个UniApply对象(是ForkJoinTask的子类,final void unipush(Completion c) {中Completion 是直接abstract static class Completion extends ForkJoinTask< Void>,即直接继承),也就是一个ForkJoinTask类型的任务,这个任务放入了第1个任务的栈当中,当他执行完自然在执行(从第一个开始)
    final void unipush(Completion c) {
        if (c != null) {
            while (!tryPushStack(c)) { //尝试将任务入栈,如果失败,则重试
                if (result != null) {
                    NEXT.set(c, null);
                    break;
                }
            }
            if (result != null) //如果上一个任务执行结束,则触发该任务的执行
                c.tryFire(SYNC);
        }
    }

每一个CompletableFuture对象内部都有一个栈,存储着是后续依赖它的任务,如下面代码所示,这个栈也就 是Treiber Stack,这里的stack存储的就是栈顶指针
volatile Object result;       // Either the result or boxed AltResult
    volatile Completion stack;    // Top of Treiber stack of dependent actions
上面的UniApply对象类似于第1步里面的AsyncSupply,它的构造方法传入了4个参数(unipush(new UniApply<T,V>(e, d, this, f));,后面的四个参数):
1:第1个参数是执行它的ForkJoinPool(ForkJoinPool是Executor的子类)
2:第2个参数是输出一个CompletableFuture对象,这个参数,也是thenApply方法的返回值,用来链式执 行下一个任务
3:第3个参数是其依赖的前置任务,也就是第1步里面提交的任务(所以是this,这样可以利用返回的对象来利用,也就是说,返回的CompletableFuture对象包含了返回值结果)
4:第4个参数是输入(也就是一个Function对象)
 static final class UniApply<T,V> extends UniCompletion<T,V> {
        Function<? super T,? extends V> fn;
        UniApply(Executor executor, CompletableFuture<V> dep,
                 CompletableFuture<T> src,
                 Function<? super T,? extends V> fn) {
            super(executor, dep, src); this.fn = fn;
        }
UniApply对象被放入了第1步的CompletableFuture的栈中(第一个自然是AsyncSupply的任务提交),在第1步的任务执行完成之后,就会从栈中弹出 并执行,如下代码:
static final class AsyncSupply<T> extends ForkJoinTask<Void>
        implements Runnable, AsynchronousCompletionTask {
        CompletableFuture<T> dep; Supplier<? extends T> fn;
        AsyncSupply(CompletableFuture<T> dep, Supplier<? extends T> fn) {
            this.dep = dep; this.fn = fn;
        }

        public final Void getRawResult() { return null; }
        public final void setRawResult(Void v) {}
        public final boolean exec() { run(); return false; }

        public void run() {
            CompletableFuture<T> d; Supplier<? extends T> f;
            if ((d = dep) != null && (f = fn) != null) {
                dep = null; fn = null;
                if (d.result == null) {
                    try {
                        d.completeValue(f.get()); //Supplier的get方法
                    } catch (Throwable ex) {
                        d.completeThrowable(ex);
                    }
                }
                d.postComplete();
            }
        }
    }
ForkJoinPool执行上面的AsyncSupply对象的run()方法,实质就是执行Supplier的get()方法(呼应之前入栈的set方法),执行结果被塞入 了 CompletableFuture d 当中,也就是赋值给了 CompletableFuture 内部的Object result变量,也就是解决了前面的这个:
 if ((r = result) != null) //使得可以进入了,即执行了
            return uniApplyNow(r, e, f);
//即很明显result是执行结果
调用d.postComplete(),也正是在这个方法里面,把第2步压入的UniApply对象弹出来执行,代码如下所示
 final void postComplete() {
        /*
         * On each step, variable f holds current dependents to pop
         * and run.  It is extended along only one path at a time,
         * pushing others to avoid unbounded recursion.
         */
        CompletableFuture<?> f = this; Completion h;
        while ((h = f.stack) != null ||
               (f != this && (h = (f = this).stack) != null)) {
            CompletableFuture<?> d; Completion t;
            if (STACK.compareAndSet(f, h, t = h.next)) { //出栈,很明显,虽然说是栈,但也可以是先进先出的,只不过方法是栈的名称而已,这里之所以这样说,是为了最好满足栈的需求,名称不重要的,只是大多数我们会认为栈是先进后出,这里可以认为是单向链表的操作,加在后面,取前面,但是一般栈虽然是后入先出,你可能会根据上面的认为先进先出了,实际上他的还是后入先出,但是他们并不能决定先后,因为当多个入栈时,是并行的,但这只是入栈并行的说明(针对入栈来说的),最终还是看栈的先后(回到后面任务一中认为任务二和任务三并行的原因)
                
                //即以后看到栈,基本上,他就是按照栈的思想来进行的,极少数只是一个名称(特别是我们自己编写的,极少数开源的会这样)
                if (t != null) {
                    if (f != this) {
                        pushStack(h);
                        continue;
                    }
                    NEXT.compareAndSet(h, t, null); // try to detach
                }
                f = (d = h.tryFire(NESTED)) == null ? this : d; //执行出栈任务,即后续的操作,所以set和get并不是字面上面的意思,其中set做个标识,而get进行赋值,即名称并不是一定符合他的要求
            }
        }
    }
//而执行结果能很明确的知道是否在运行,并且其中我们可以看到,在thenApply中CompletableFuture 是他创建的,所以说,对应的结果是不共享的,即在前面的案例CompletableFutureDemo7中才会说明"注意他们是不同的对象了,自然赋值的返回值是不同的,所以需要后续的操作",但是也要明白,就算是相同的,那么返回值应该互通而不是为空,且一直存在,但是可能也会有压入栈的情况,只是对应的操作可能不是判断返回值是否为空了,一般有其他标识,这里就不多说了
第3步:CompletableFuture future3=future2.thenRun()
第3步和第2步的过程类似,构建了一个 UniRun 对象,这个对象被压入第2步的CompletableFuture所在的栈 中的对应第2步的任务,当执行完成时,从自己的栈中弹出UniRun对象并执行
综上所述:
通过supplyAsync/thenApply/thenRun,分别提交了3个任务,每1个任务都有1个返回值对象,也就是1个CompletableFuture,这3个任务通过2个CompletableFuture完成串联,后1个任务,被放入了前1个任务的CompletableFuture里面,前1个任务在执行完成时,会从自己的栈中,弹出下1个任务执行,如此向后传递,完成 任务的链式执行

在这里插入图片描述

thenApply与thenApplyAsync的区别:
public <U> CompletableFuture<U> thenApply(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }

    public <U> CompletableFuture<U> thenApplyAsync(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(defaultExecutor(), fn);
    }

//主要说明两个参数,不考虑重载
//但最终的这个uniApplyStage方法还是一样的
 private <V> CompletableFuture<V> uniApplyStage(
        Executor e, Function<? super T,? extends V> f) {
        if (f == null) throw new NullPointerException();
        Object r;
        if ((r = result) != null) 
            return uniApplyNow(r, e, f);
        CompletableFuture<V> d = newIncompleteFuture();
        unipush(new UniApply<T,V>(e, d, this, f));
        return d;
    }
对于上一个任务已经得出结果的情况,那么直接的执行return uniApplyNow(r, e, f);:
 private <V> CompletableFuture<V> uniApplyNow(
        Object r, Executor e, Function<? super T,? extends V> f) {
        Throwable x;
        CompletableFuture<V> d = newIncompleteFuture();
        if (r instanceof AltResult) {
            if ((x = ((AltResult)r).ex) != null) {
                d.result = encodeThrowable(x, r);
                return d;
            }
            r = null;
        }
  
        try {
            if (e != null) { //e!=null表示是thenApplyAsync,看前面的执行就知道了
                e.execute(new UniApply<T,V>(null, d, this, f));
            } else { //否则就是thenApply方法
                   /*
     首先,将第一个任务的结果转换为T类型,然后直接调用Funtion的apply方法,而apply方法就是我们传值的Function对象中实现的apply方法,即我们接收上一个任务的结果(t)作为参数了,这就是为什么我们得到了上一个任务的结果的原因
     */
                @SuppressWarnings("unchecked") T t = (T) r;
                d.result = d.encodeValue(f.apply(t));
            }
        } catch (Throwable ex) {
            d.result = encodeThrowable(ex);
        }
        return d;
    }
如果e != null表示是thenApplyAsync,需要调用ForkJoinPool的execute方法,该方法:
public void execute(Runnable task) {
        if (task == null)
            throw new NullPointerException();
        ForkJoinTask<?> job;
        if (task instanceof ForkJoinTask<?>) // avoid re-wrap
            job = (ForkJoinTask<?>) task;
        else
            job = new ForkJoinTask.RunnableExecuteAction(task);
        externalSubmit(job); //提交任务
    }

private <T> ForkJoinTask<T> externalSubmit(ForkJoinTask<T> task) {
        Thread t; ForkJoinWorkerThread w; WorkQueue q;
        if (task == null)
            throw new NullPointerException();
        if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) &&
            (w = (ForkJoinWorkerThread)t).pool == this &&
            (q = w.workQueue) != null)
            q.push(task); //将任务入栈上一个任务的栈,之后取出执行
        else
            externalPush(task);
        return task;
    }
通过上面的代码可以看到:
1:如果前置任务没有完成,即a.result=null,thenApply和thenApplyAsync都会将当前任务的下一个任务 入栈,然后再出栈执行
2:只有在当前任务已经完成的情况下,thenApply才会立即执行,不会入栈,再出栈,不会交给ForkJoinPool,thenApplyAsync还是将下一个任务封装为ForkJoinTask,入栈,之后出栈再执行,保证有顺序(应该是满足多线程的),注意:这里之前的清空返回值的说明还是存在的
同理,thenRun与thenRunAsync、thenAccept与thenAcceptAsync的区别与此类似
任务的网状执行:有向无环图:
如果任务只是链式执行,便不需要在每个CompletableFuture里面设1个栈了,用1个指针使所有任务组成链表 即可
但实际上,任务不(只)是链式执行,而是网状执行,组成 1 张图,如下图所示,所有任务组成一个有向无环图(有方向,但是没有有成环的):

在这里插入图片描述

任务一执行完成之后,任务二、任务三可以并行(前面有回答这个,不是真的并行哦,后面的并行说明还是正常并行的,因为多次执行的结果会有不一样的),在代码层面可以写为:future1.thenApply(任务二),future1.thenApply(任务三),不是逗号,那么
任务四在任务二执行完成时可开始执行
任务五要等待任务二、任务三都执行完成,才能开始,这里是AND关系(即相当于前面的allOf)
任务六在任务三执行完成时可以开始执行
对于任务七,只要任务四、任务五、任务六中任意一个任务结束,就可以开始执行
总而言之,任务之间是多对多的关系:1个任务有n个依赖它的后继任务(任务一的任务二和任务三),1个任务也有n个它依赖的前驱任务(任务五的任务二和任务三)
那么我们一般在前面学习时,有过AND关系(与),以及单纯的指向关系(链式),那么后继的有没有出现呢,答:实际上后继的就是OR关系,即有过案例了(或)
这样一个有向无环图,用什么样的数据结构表达呢?AND和OR的关系又如何表达呢?
有⼏个关键点:
1:在每个任务的返回值里面,存储了依赖它的接下来要执行的任务(栈),所以在上图中,任务一的CompletableFuture的栈中存储了任务二、任务三,而任务二的CompletableFuutre中存储了任务四、任务 五,任务三的CompletableFuture中存储了任务五、任务六,即每个任务的CompletableFuture对象的栈 里面,其实存储了该节点的出边对应的任务集合(前提是放入了栈),但是一般是会存储的,因为执行需要的时间通常大于判断时间(所以一般必然是判断的,即会放入栈)
2:任务二、任务三的CompletableFuture里面,都存储了任务五,那么任务五是不是会被触发两次,执行两 次呢? 任务五的确会被触发二次,但它会判断任务二、任务三的结果是不是都完成,如果只完成其中一个,它就 不会执行(具体我们并没有说明AND关系的底层原理,所以了解即可)
3:任务七存在于任务四、任务五、任务六的CompletableFuture的栈里面,因此会被触发三次,但它只会执 行一次,只要其中1个任务执行完成,就可以执行任务七了(即OR,我们也没有说明,所以了解即可)
4:正因为有AND和OR两种不同的关系,因此对应BiApply和OrApply两个对象,这两个对象的构造方法⼏乎 一样,只是在内部执行的时候,一个是AND的逻辑,一个是OR的逻辑(你可以在CompletableFutureDemo11中的run方法里面打印System.out.println(Thread.currentThread());来多次的执行(Thread.currentThread()可以获取当前线程的引用),就可以发现是不同的,即的确是并行的,所以由于是并行的(无论and还是or都是并行的,这里也可也在CompletableFutureDemo12中进行测试),那么我们的确只需要一个有返回值就行了)
 static final class BiApply<T,U,V> extends BiCompletion<T,U,V> {
        BiFunction<? super T,? super U,? extends V> fn;
        BiApply(Executor executor, CompletableFuture<V> dep,
                CompletableFuture<T> src, CompletableFuture<U> snd,
                BiFunction<? super T,? super U,? extends V> fn) {
            super(executor, dep, src, snd); this.fn = fn;
        }
static final class OrApply<T,U extends T,V> extends BiCompletion<T,U,V> {
        Function<? super T,? extends V> fn;
        OrApply(Executor executor, CompletableFuture<V> dep,
                CompletableFuture<T> src, CompletableFuture<U> snd,
                Function<? super T,? extends V> fn) {
            super(executor, dep, src, snd); this.fn = fn;
        }
所以我们其实也可也出现这样:

在这里插入图片描述

BiApply和OrApply都是二元操作符,也就是说,只能传入二个被依赖的任务,但上面的任务七同时依赖 于任务四、任务五、任务六,这怎么处理呢?
任何一个多元操作,都能被转换为多个二元操作的叠加,如上图所示,假如任务一AND任务二AND任务 三 ==> 任务四,那么它可以被转换为右边的形式,新建了一个AND任务,这个AND任务(上面也成为任务一了)和任务三再作为 参数,构造任务四,OR的关系,与此类似,当然,前面说明的anyOf和allOf的任务基本也是这样,而正是因为可能会分成(必然)有两个一起,所以你在多次执行时,会出现不同(上面说明的在CompletableFutureDemo11或者CompletableFutureDemo12中加上对应的打印,在结果上,基本只是两个变化而已,看打印就知道了,必然前一个是3,后一个是5,那么基本只是他们直接变化,后面的组合也是如此,大体顺序不变)
此时,thenCombine的内部实现原理也就可以解释了,thenCombine用于任务一、任务二执行完成,再执行 任务三这样的操作,前面没有这样说明,这里补充一下,其中无论是and还是or他们都是并行的,但是thenCombine还是有顺序的,因为他没有操作and或者or
allOf内部的计算图分析(and):
下面以allOf方法为例,看一下有向无环计算图的内部运作过程:
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
        return andTree(cfs, 0, cfs.length - 1);
    }

static CompletableFuture<Void> andTree(CompletableFuture<?>[] cfs,
                                           int lo, int hi) {
        CompletableFuture<Void> d = new CompletableFuture<Void>();
        if (lo > hi) // empty
            d.result = NIL;
        else {
            CompletableFuture<?> a, b; Object r, s, z; Throwable x;
            int mid = (lo + hi) >>> 1;
            if ((a = (lo == mid ? cfs[lo] :
                      andTree(cfs, lo, mid))) == null ||
                (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] :
                      andTree(cfs, mid+1, hi))) == null)
                throw new NullPointerException();
            if ((r = a.result) == null || (s = b.result) == null)
                //任务d被分别压入a和b所在的栈,当a和b执行结束,就可以接着执行d了
                a.bipush(b, new BiRelay<>(d, a, b));
            else if ((r instanceof AltResult
                      && (x = ((AltResult)(z = r)).ex) != null) ||
                     (s instanceof AltResult
                      && (x = ((AltResult)(z = s)).ex) != null))
                d.result = encodeThrowable(x, z);
            else
                d.result = NIL;
        }
        return d;
    }

上面的方法是一个递归方法,输入是一个CompletableFuture对象的列表,输出是一个具有AND关系的复合CompletableFuture对象
最关键的代码:如上面加注释部分所示,因为d要等a,b都执行完成之后才能执行,因此d会被分别压入a,b所 在的栈中,通过这里的说明,我们可以得到一个原理,前面的返回值只要存在你的出栈完毕,那么就会赋值(最后操作的得到,上面就是b,所以b是返回的对应对象,自然得到的是他的返回值结果,虽然get是等待获取,但是实际上内部他们也是需要等待获取的,只是get是我们手动的获取的,而他们是直接的获取(T类型那里,这是因为不是最后一个),可以说get是一个简单的T类型操作,虽然这里没有说明),否则不会赋值,而出栈完毕,就是代表你的d全部出栈完毕
 final void bipush(CompletableFuture<?> b, BiCompletion<?,?,?> c) {
        if (c != null) {
            while (result == null) {
                if (tryPushStack(c)) { //准备将c入栈a任务
                    if (b.result == null)
                        b.unipush(new CoCompletion(c)); //将c入栈b任务
                    else if (result != null)
                        c.tryFire(SYNC);
                    return;
                }
            }
            b.unipush(c);
        }
    }

 final void unipush(Completion c) {
        if (c != null) {
            while (!tryPushStack(c)) {
                if (result != null) {
                    NEXT.set(c, null);
                    break;
                }
            }
            if (result != null)
                c.tryFire(SYNC);
        }
    }
下图为allOf内部的运作过程,假设allof的参数传入了future1、future2、future3、future4(我们的案例中就是数组的形式,因为是可变长参数,我们看出一维数组即可),则对应四个原始 任务

在这里插入图片描述

⽣成BiRelay1、BiRelay2任务(固定的类任务,之前的也是对应的内部类操作,每个参数一个内部类任务提交,只是这里是多个),分别压入future1/future2、future3/future4的栈中,无论future1或future2完成,都会触发BiRelay1,无论future3或future4完成,都会触发BiRelay2,得到其中一个返回值操作
⽣成BiRelay3任务,压入future5/future6的栈中,无论future5或future6完成,都会触发BiRelay3任务
BiRelay只是一个中转任务,它本身没有任务代码,只是参照输入的两个future是否完成,如果完成,就从自己 的栈中弹出依赖它的BiRelay任务,然后执行
ForkJoinPool:
ForkJoinPool用法:
ForkJoinPool就是JDK7提供的一种"分治算法"(思想)的多线程并行计算框架,Fork意为分叉,Join意为合并,一分一 合,相互配合,形成分治算法(思想),此外,也可以将ForkJoinPool看作一个单机版的Map/Reduce,多个线程并行计 算
相比于ThreadPoolExecutor,ForkJoinPool可以更好地实现计算的负载均衡,提高资源利用率,所以之前的都是使用ForkJoinPool来完成任务的处理,虽然ThreadPoolExecutor和ForkJoinPool的最终父类都是Executor接口,即基本都是直接继承AbstractExecutorService到继承ExecutorService到实现Executor
假设有5个任务,在ThreadPoolExecutor中有5个线程(核心为5)并行执行,其中一个任务的计算量很大,其余4个任务的 计算量很小,这会导致1个线程很忙,其他4个线程则容易处于空闲状态(核心线程是存在且没有销毁的)
利用ForkJoinPool,可以把大的任务拆分成很多小任务,然后这些小任务被所有的线程执行,从而实现任务计 算的负载均衡
例子1:快排
快排有2个步骤:
1:利用数组的第1个元素把数组划分成两半,左边数组里面的元素小于或等于该元素,右边数组里面的元素 比该元素大
2:对左右的两个子数组分别排序
左右两个子数组相互独立可以并行计算,利用ForkJoinPool,代码如下:
package main7;

import java.util.Arrays;
import java.util.concurrent.*;

/**
 *
 */
public class ForkJoinPoolDemo01 {
    static class SortTask extends RecursiveAction {
        final long[] array;
        final int lo;
        final int hi;

        public SortTask(long[] array) {
            this.array = array;
            this.lo = 0;
            this.hi = array.length - 1;
        }

        public SortTask(long[] array, int lo, int hi) {
            this.array = array;
            this.lo = lo;
            this.hi = hi;
        }

        //以最终的数组长度为基准,而不是以开始为基准,只是换个位置而已,具体实现还是一样的
        private int partition(long[] array, int lo, int hi) {
            long x = array[hi];
            int i = lo - 1;
            for (int j = lo; j < hi; j++) {
                if (array[j] <= x) {
                    i++;
                    swap(array, i, j);
                }
            }
            swap(array, i + 1, hi);
            return i + 1;
        }

        private void swap(long[] array, int i, int j) {
            if (i != j) {
                long temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }

        @Override
        protected void compute() {
            if (lo < hi) {
                // 找到分区的元素下标
                int pivot = partition(array, lo, hi); //也就是对应的位置
                // 将数组分为两部分
                SortTask left = new SortTask(array, lo, pivot - 1);
                SortTask right = new SortTask(array, pivot + 1, hi);

                //分叉
                Object fork = left.fork();
                Object fork1 = right.fork();
                //这里是对应的ForkJoinTask submit = pool.submit(sort);的submit的相关类似打印结果,虽然一边他会打印,因为这里要慢点
                System.out.println(fork);
                System.out.println(fork1);
                //合并
                //一般这个返回值类型与当前方法的返回值类型一致,若这里是void,那么他返回Void,虽然都可以使用Object来接收
                Object join = left.join();
                Object join1 = right.join();
                //这里基本都是null
                System.out.println(join);
                System.out.println(join1);

                //因为多次的执行这个方法,所以打印有很多

                //我们知道,在快排时,我们也会进行分开,即二分,但是这里是每个线程进行操作,最后合并,而不是一个线程操作,然后合并
                //当然,由于地址的原因,我们实际上不用合并也行,因为他只是将对应的地址拿走而已,所以你可以选择删除上面的join
                //所以说,对应的fork,实际上就是继续执行compute方法,所以你将fork修改成compute也可,但是修改成compute那么只是一个线程来进行操作的,而不是fork由多个线程来操作(或者说由另外一个线程执行compute)
                //现在我们来说明这个合并,他这个合并一般也是需要调用某个方法的,虽然这里可能没有写,那么一般就是默认空的操作,就如RecursiveAction类里面的setRawResult是空操作一样
            }
        }

    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long[] array = {5, 3, 7, 9, 2, 4, 1, 8, 10};
        // 一个任务
        ForkJoinTask sort = new SortTask(array);
        // 一个pool
        ForkJoinPool pool = new ForkJoinPool();
        // ForkJoinPool开启多个线程,同时执行上面的子任务
        //参数需要ForkJoinTask类型的,其中SortTask继承的类RecursiveAction也继承了ForkJoinTask类型,所以对应需要继承RecursiveAction
        //并且我们基本继承RecursiveAction,因为我们需要利用他里面的compute方法,即我们执行的是这个方法,而不是单纯的继承ForkJoinTask,那么可以我们继承ForkJoinTask并且自己实现该方法吗,答:虽然看起来没有问题,但是我们实际上还是利用的RecursiveAction里面的其他方法,所以我们还是需要的,否则可能不会操作,特别是如:
        /*
        protected final boolean exec() {
        compute();
        return true;
    }
    //他一般是源头,所以如果你不继承RecursiveAction,而继承ForkJoinTask,那么将他的对应的方法拿过来即可:
    即 
    @Override
        protected boolean exec() {
            compute();
            return true;
        }
        那么结果就是一样的了,所以exec是源头,像这样的源头的说明,后面的其他操作就不做大致的了解了
        */
        ForkJoinTask submit = pool.submit(sort);
        System.out.println(submit); //main7.ForkJoinPoolDemo01$SortTask@506e6d5e根据sort的内容来进行的,所以如果你的内容与我一样,那么这个值可能也相同(当然可能与机器也会有关系)
        System.out.println(2);
        Object o = submit.get();
        System.out.println(o+"1"); //null,是没有返回值的,要记住
        // 结束ForkJoinPool
        pool.shutdown();
        // 等待结束Pool,操作等待10秒
        pool.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println(Arrays.toString(array));
    }

}

至此大致说明完毕,简单来说就是多个线程来操作对应的二分,而提高效率,而不是必须一步一步走,使得可能需要等待一边数组操作完毕才可操作另外一边
例子2:求1到n个数的和
package main7;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 *
 */
public class ForkJoinPoolDemo02 {
    static class SumTask extends RecursiveTask<Long> {
        private static final int THRESHOLD = 10;
        private long start;
        private long end;

        public SumTask(long n) {
            this(1, n);
        }

        public SumTask(long start, long end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Long compute() {
            long sum = 0;
            // 如果计算的范围在threshold之内,则直接进行计算
            if ((end - start) <= THRESHOLD) {
                for (long l = start; l <= end; l++) {
                    sum += l;
                }
            } else {
                // 否则找出起始和结束的中间值,分割任务
                long mid = (start + end) >>> 1; //逻辑右移动,那么自然会除以2,由于移动时会清除1,所以若是101,那么结果是50,而对于左移来说,不好清除1,所以是真的乘以2,而不是取整(二进制清除了1,那么对于十进制来说就是取整)
                SumTask left = new SumTask(start, mid);
                SumTask right = new SumTask(mid + 1, end);
                left.fork();
                right.fork();
                // 收集子任务计算结果
                Long join = left.join(); //前面的返回值类型
                sum = left.join() + right.join();
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return sum;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SumTask sum = new SumTask(100);
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> future = pool.submit(sum);
        Long aLong = future.get(); //照样操作阻塞
        System.out.println(aLong); //有返回值了,5050,得到的是直接的返回,即内部里面的fork不算,就是直接方法的返回,因为本来就是得到你的调用,你内部如何调用不做要求
        pool.shutdown(); //相当于线程池的关掉,因为我们认为是线程池的,即相关作用基本相同,只是他的线程可以操作分开的任务
    }
}

所以可以发现,只要是可以进行分开的,基本都可以利用ForkJoinPool来进行优化,没有返回值也行,有返回值也行,都可以,虽然线程池也可以,但是他是封装好的,而其他线程池只是给出多个线程,但他们之间的联系没有给出,或者说只是操作任务而不会去自动的操作分开的任务,所以在这种情况下,一般我们都会用ForkJoinPool来进行优化,上面案例中大致指定流程即可,可能有细节问题,但这里就不多说了,其中join虽然是阻塞的,但是任务还是分开执行的,他只是使得当前线程不会执行完毕或者说等待任务返回结果而已,后面会说明的
上面的代码用到了 RecursiveAction 和 RecursiveTask 两个类,它们都继承自抽象类ForkJoinTask,用到了其 中关键的接口 fork()、join(),二者的区别是一个有返回值,一个没有返回值(RecursiveTask 里面有这个变量V result;,并且对应的方法有返回值,特别是compute方法,而不是RecursiveAction类的compute方法的空返回值,所以要操作返回值时,记得实现RecursiveTask)
public abstract class RecursiveAction extends ForkJoinTask<Void> {
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
RecursiveAction/RecursiveTask类继承关系:

在这里插入图片描述

白色实线箭头继承关系,白色虚线箭头实现关系
在ForkJoinPool中,对应参数的接口或者类如下:
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) { //这个参数,间接的的自动变成对应的泛型类型(这里是Void,传递的参数最终是继承ForkJoinTask<Void>的或者ForkJoinTask<V>的,后面的ForkJoinTask<V>是RecursiveTask继承的,前面是RecursiveAction继承的)也是可以的
        return externalSubmit(task);
    }
核⼼数据结构:
与ThreadPoolExector不同的是,除一个全局的任务队列之外(直接调用的任务,上面的例子基本都是一个全局,多个局部),每个线程还有一个自己的局部队列(使得线程的确可以操作分开的任务,一般来说局部完成后,才操作全局)

在这里插入图片描述

核⼼数据结构如下所示:
public class ForkJoinPool extends AbstractExecutorService {
 
 // 状态变量,类似于ThreadPoolExecutor中的ctl变量
 volatile long ctl;
 // ⼯作线程队列(局部)
 WorkQueue[] workQueues;
 // ⼯作线程⼯⼚,一般代表线程集合
 final ForkJoinWorkerThreadFactory factory;
 // 下一个worker的下标
 int indexSeed;
 
 static final class WorkQueue {
 volatile int source; // source queue id, or sentinel
 int id; // 在ForkJoinPool的workQueues数组中的下标
 int base; // 队列尾部指针
 int top; // 队列头指针
 volatile int phase; // versioned, negative: queued, 1: locked
 int stackPred; // pool stack (ctl) predecessor link
 int nsteals; // number of steals
 ForkJoinTask<?>[] array; // ⼯作线程的局部队列
 final ForkJoinPool pool; // the containing pool (may be null)
 final ForkJoinWorkerThread owner; // 该⼯作队列的所有者线程,null表示共享的(一开始要选择的,自然共享,选择后就不共享了),否则是局部,当是null时,一般上面代表全局队列或者全局队列在其他的地方(一般线程池也可也扩容,只是通常会操作拒绝策略而已,这里姑且认为是其他地方,虽然是这个也是可以扩容的),这里了解即可

 }
}

public class ForkJoinWorkerThread extends Thread {
 // 当前⼯作线程所在的线程池,反向引⽤
 final ForkJoinPool pool;
 // ⼯作队列
 final ForkJoinPool.WorkQueue workQueue; //当前线程的工作队列
}
下面看一下这些核⼼数据结构的构造过程
public ForkJoinPool() { //如果不指定并发数,默认为CPU核心数(保证平均)
        this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
             defaultForkJoinWorkerThreadFactory, null, false,
             0, MAX_CAP, 1, null, DEFAULT_KEEPALIVE, TimeUnit.MILLISECONDS);
    }
//上面的this说明的构造
 public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode,
                        int corePoolSize,
                        int maximumPoolSize,
                        int minimumRunnable,
                        Predicate<? super ForkJoinPool> saturate,
                        long keepAliveTime,
                        TimeUnit unit) {
        // check, encode, pack parameters
        if (parallelism <= 0 || parallelism > MAX_CAP ||
            maximumPoolSize < parallelism || keepAliveTime <= 0L)
            throw new IllegalArgumentException();
        if (factory == null)
            throw new NullPointerException();
        long ms = Math.max(unit.toMillis(keepAliveTime), TIMEOUT_SLOP);

        int corep = Math.min(Math.max(corePoolSize, parallelism), MAX_CAP);
        long c = ((((long)(-corep)       << TC_SHIFT) & TC_MASK) |
                  (((long)(-parallelism) << RC_SHIFT) & RC_MASK));
        int m = parallelism | (asyncMode ? FIFO : 0);
        int maxSpares = Math.min(maximumPoolSize, MAX_CAP) - parallelism;
        int minAvail = Math.min(Math.max(minimumRunnable, 0), MAX_CAP);
        int b = ((minAvail - parallelism) & SMASK) | (maxSpares << SWIDTH);
        int n = (parallelism > 1) ? parallelism - 1 : 1; // at least 2 slots
        n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16;
        n = (n + 1) << 1; // power of two, including space for submission queues

     // ⼯作线程名称前缀
        this.workerNamePrefix = "ForkJoinPool-" + nextPoolId() + "-worker-";
     // 初始化⼯作线程数组为n,2的幂次⽅
        this.workQueues = new WorkQueue[n];
     // worker线程⼯⼚,有默认值
        this.factory = factory;
        this.ueh = handler;
        this.saturate = saturate;
        this.keepAlive = ms;
        this.bounds = b;
        this.mode = m;
     // ForkJoinPool的状态
        this.ctl = c;
        checkPermission();
    }
工作窃取队列:
关于上面的全局队列,有一个关键点需要说明:它并⾮使用BlockingQueue,而是基于一个普通的数组得以实现
这个队列又名工作窃取队列,为 ForkJoinPool 的工作窃取算法提供服务,在 ForkJoinPool开篇的注释中,Doug Lea 特别提到了工作窃取队列的实现,其陈述来自如下两篇论文:"Dynamic Circular Work-Stealing Deque" by Chase and Lev,SPAA 2005与 " Idempotent work stealing" by Michael,Saraswat,and Vechev,PPoPP 2009",你可以在网上查阅相应论文(虽然这个资料也是网上找的,大多数博客也是这样说的)
所谓工作窃取算法,是指一个Worker线程在执行完毕自己队列中的任务之后,可以窃取其他线程队列中的任 务来执行,从而实现负载均衡,以防有的线程很空闲,有的线程很忙(运气不好,都被他拿到了,可以认为是CAS的抢占),这个过程要用到工作窃取队列,认为是继续操作任务(就如前面案例中线程操作分开的一样,只是该线程不是新的,而是原来的核心线程,因为我们认为对应的ForkJoinPool是类似于线程池的,所以相关作用基本相同,只是他的线程可以操作分开的任务)

在这里插入图片描述

这个队列只有如下⼏个操作:
1:Worker线程自己,在队列头部,通过对top指针执行加、减操作,实现入队或出队,这是单线程的(只是对该操作而言,他里面有锁,所以不需要CAS,前面说明对应的线程池时基本操作锁了)
2:其他Worker线程,在队列尾部,通过对base进行累加,实现出队操作,也就是窃取,这是多线程的,需要通过CAS操作(多个人窃取)
这个队列,在Dynamic Circular Work-Stealing Deque这篇论文中被称为dynamic-cyclic-array,之所以这样命 名,是因为有两个关键点:
1:整个队列是环形的,也就是一个数组实现的RingBuffer,并且base会一直累加,不会减小,top会累加或者减小,所以最后base、top的总值基本都会大于整个数组的长度,只是计算数组下标的时候(top是可以为0的),会取top&(queue.length-1),base&(queue.length-1),因为queue.length是2的整数次方,这里也就是对queue.length进行取模操作,当top-base=queue.length-1 的时候,队列为满(因为base是出队的,为加,而top入队为加),此时需要扩容,当top=base的时候,队列为空(即都出队了),Worker线程即将进入阻塞状态(核心的空闲)
2:当队列满了之后会扩容,且被称为是动态的,即动态扩容,但这就涉及一个棘⼿的问题:多个线程同时在读写这个队 列,如何实现在不加锁的情况下一边读写、一边扩容呢?,虽然说不加锁,但是基本还是利用锁的思想或者加上了乐观锁,所以一般来说不加的锁是说明不加悲观锁的操作的
这是大多数多线程扩容时要面临的问题,每个框架或者其他代码都有对应自己的解决方式(根据自己的作用来进行解决的),这里通过分析工作窃取队列的特性,我们会发现:在 base 一端,是多线程访问的,但它们只会使base变大,也就 是使队列中的元素变少,所以队列为满,一定发⽣在top一端,对top进行累加的时候,这一端却是单线程的,队列 的扩容恰好利用了这个单线程的特性,即在扩容过程中,不可能有其他线程对top进行修改,只有线程对base进行 修改!
下图为工作窃取队列扩容示意图,扩容之后,数组长度变成之前的二倍,但top、base的值是不变的,通过top、base对新的数组长度取模,仍然可以定位到元素(任务)在新数组中的位置

在这里插入图片描述

从上面可以看出,进行了扩容(创建新的队列),由于单线程这个地方不会有人进入,即不会影响扩容,且后面的扩容也利用了CAS,而之所以利用CAS是保证没有base不会影响,使得移动过去,所以综上所述,base和top都不会影响扩容
下面结合WorkQueue扩容的代码进一步分析:
 public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
        return externalSubmit(task);
    }

 private <T> ForkJoinTask<T> externalSubmit(ForkJoinTask<T> task) {
        Thread t; ForkJoinWorkerThread w; WorkQueue q;
        if (task == null)
            throw new NullPointerException();
        if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) &&
            (w = (ForkJoinWorkerThread)t).pool == this &&
            (q = w.workQueue) != null)
            q.push(task); //这个地方
        else
            externalPush(task);
        return task;
    }

  final void push(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a;
            int s = top, d, cap, m;
            ForkJoinPool p = pool;
            if ((a = array) != null && (cap = a.length) > 0) {
                QA.setRelease(a, (m = cap - 1) & s, task);
                top = s + 1;
                if (((d = s - (int)BASE.getAcquire(this)) & ~1) == 0 &&
                    p != null) {                 // size 0 or 1
                    VarHandle.fullFence();
                    p.signalWork();
                }
                else if (d == m)
                    growArray(false); //这个地方是扩容的主要方法,这里是局部,一般来说线程池队列是没有扩容的,有定义参数来进行边界的,但这里不是一般的(可能包含全局队列,前面说的null为共享的地方)
            }
        }


  final void growArray(boolean locked) {
            ForkJoinTask<?>[] newA = null;
            try {
                ForkJoinTask<?>[] oldA; int oldSize, newSize;
                // 当旧的array不是null,旧的array包含元素
                // 并且新的数组长度小于队列最大长度,并且新的长度大于0
                if ((oldA = array) != null && (oldSize = oldA.length) > 0 &&
                    (newSize = oldSize << 1) <= MAXIMUM_QUEUE_CAPACITY &&
                    newSize > 0) {
                    try {
                        // 创建新数组
                        newA = new ForkJoinTask<?>[newSize];
                    } catch (OutOfMemoryError ex) {
                    }
                    if (newA != null) { // poll from old array, push to new
                        int oldMask = oldSize - 1, newMask = newSize - 1;
                        for (int s = top - 1, k = oldMask; k >= 0; --k) {
                            // 逐个复制,即扩容,创建新的队列
                            ForkJoinTask<?> x = (ForkJoinTask<?>)
                                // 获取旧的值,将原来的设置为null
                                //static final VarHandle QA;
                                QA.getAndSet(oldA, s & oldMask, null); //这里也是CAS操作
                            if (x != null)
                                newA[s-- & newMask] = x;
                            else
                                break;
                        }
                        array = newA;
                        VarHandle.releaseFence(); //实际上在idea中若是调用类的类的静态方法,那么不会对该方法有特别的颜色,而变量则会,无论是否是他自身调用使用(QA)还是类调用使用(class(一个类,这里简称class).i,i是静态的)
                    }
                }
            } finally {
                if (locked)
                    phase = 0;
            }
            if (newA == null)
                throw new RejectedExecutionException("Queue capacity exceeded");
        }

ForkJoinPool状态控制:
状态变量ctl解析:
类似于ThreadPoolExecutor,在ForkJoinPool中也有一个ctl变量负责表达ForkJoinPool的整个⽣命周期和相关的各种状态,不过这里的ctl变量更加复杂,是一个long型变量,代码如下所示
public class ForkJoinPool extends AbstractExecutorService {
 // ...

 // 线程池状态变量

 volatile long ctl;
 private static final long SP_MASK = 0xffffffffL;
 private static final long UC_MASK = ~SP_MASK;
 private static final int RC_SHIFT = 48;
 private static final long RC_UNIT = 0x0001L << RC_SHIFT;
 private static final long RC_MASK = 0xffffL << RC_SHIFT;
 private static final int TC_SHIFT = 32;
 private static final long TC_UNIT = 0x0001L << TC_SHIFT;
 private static final long TC_MASK = 0xffffL << TC_SHIFT;
 private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15); // sign

 
 // ...

}
public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode,
                        int corePoolSize,
                        int maximumPoolSize,
                        int minimumRunnable,
                        Predicate<? super ForkJoinPool> saturate,
                        long keepAliveTime,
                        TimeUnit unit) {
        // check, encode, pack parameters
        if (parallelism <= 0 || parallelism > MAX_CAP ||
            maximumPoolSize < parallelism || keepAliveTime <= 0L)
            throw new IllegalArgumentException();
        if (factory == null)
            throw new NullPointerException();
        long ms = Math.max(unit.toMillis(keepAliveTime), TIMEOUT_SLOP);

        int corep = Math.min(Math.max(corePoolSize, parallelism), MAX_CAP);
    //这个地方
    /*
    private static final int  TC_SHIFT   = 32;
    private static final long TC_MASK    = 0xffffL << TC_SHIFT;
    private static final int  RC_SHIFT   = 48;
    private static final long RC_MASK    = 0xffffL << RC_SHIFT;
    */
        long c = ((((long)(-corep)       << TC_SHIFT) & TC_MASK) |
                  (((long)(-parallelism) << RC_SHIFT) & RC_MASK));
        int m = parallelism | (asyncMode ? FIFO : 0);
        int maxSpares = Math.min(maximumPoolSize, MAX_CAP) - parallelism;
        int minAvail = Math.min(Math.max(minimumRunnable, 0), MAX_CAP);
        int b = ((minAvail - parallelism) & SMASK) | (maxSpares << SWIDTH);
        int n = (parallelism > 1) ? parallelism - 1 : 1; // at least 2 slots
        n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16;
        n = (n + 1) << 1; // power of two, including space for submission queues

        this.workerNamePrefix = "ForkJoinPool-" + nextPoolId() + "-worker-";
        this.workQueues = new WorkQueue[n];
        this.factory = factory;
        this.ueh = handler;
        this.saturate = saturate;
        this.keepAlive = ms;
        this.bounds = b;
        this.mode = m;
        this.ctl = c; //这个地方
        checkPermission();
    }
ctl变量的64个比特位被分成五部分:
1:AC:最高的16个比特位,表示Active线程数-parallelism,parallelism是上面的构造方法传进去的参数
2:TC:次高的16个比特位,表示Total线程数-parallelism
3:ST:1个比特位,如果是1,表示整个ForkJoinPool正在关闭
4:EC:15个比特位,表示阻塞栈的栈顶线程的wait count(关于什么是wait count,接下来解释)
5:ID:16个比特位,表示阻塞栈的栈顶线程对应的id

在这里插入图片描述

阻塞栈Treiber Stack(后面相关的知识只需要了解即可,因为只是大致说明,并没有很细节的说明):
什么叫阻塞栈呢?
要实现多个线程的阻塞、唤醒,除了park/unpark这一对操作原语,还需要一个无锁链表实现的阻塞队列,把 所有阻塞的线程串在一起
在ForkJoinPool中,没有使用阻塞队列,而是使用了阻塞栈,把所有空闲的Worker线程放在一个栈里面,这个 栈同样通过链表来实现,名为Treiber Stack,前面讲解Phaser的实现原理的时候,也用过这个数据结构
下图为所有阻塞的Worker线程组成的Treiber Stack

在这里插入图片描述

⾸先,WorkQueue有一个id变量,记录了自己在WorkQueue[]数组中的下标位置,id变量就相当于每个WorkQueue或ForkJoinWorkerThread对象的地址
//ForkJoinPool类里面的 
WorkQueue[] workQueues;              // main registry
//ForkJoinPool类里面的WorkQueue内部类里面的
int id;   
其次,ForkJoinWorkerThread还有一个stackPred变量,记录了前一个阻塞线程的id,这个stackPred变量就 相当于链表的next指针,把所有的阻塞线程串联在一起,组成一个Treiber Stack
最后,ctl变量的最低16位,记录了栈的栈顶线程的id,中间的15位,记录了栈顶线程被阻塞的次数,也称为wait count
是否感觉所有的id都是串起来的一样的,也的确是这样的
ctl变量的初始值:
构造方法中,有如下的代码:
    /*
    private static final int  TC_SHIFT   = 32;
    private static final long TC_MASK    = 0xffffL << TC_SHIFT;
    private static final int  RC_SHIFT   = 48;
    private static final long RC_MASK    = 0xffffL << RC_SHIFT;
    */
long c = ((((long)(-corep)       << TC_SHIFT) & TC_MASK) |
                  (((long)(-parallelism) << RC_SHIFT) & RC_MASK));
因为在初始的时候,ForkJoinPool 中的线程个数为 0,所以 AC=0-parallelism,TC=0-parallelism,这意味着 只有高32位的AC、TC 两个部分填充了值,低32位都是0填充
ForkJoinWorkerThread状态与个数分析:
public class ForkJoinWorkerThread extends Thread {
在ThreadPoolExecutor中,有corePoolSize和maxmiumPoolSize 两个参数联合控制总的线程数,而在ForkJoinPool中只传入了一个parallelism参数,且这个参数并不是实际的线程数,那么,ForkJoinPool在实际的运 行过程中,线程数究竟是由哪些因素决定的呢?
要回答这个问题,先得明⽩ForkJoinPool中的线程都可能有哪⼏种状态?可能的状态有三种:
1:空闲状态(放在Treiber Stack里面)
2:活跃状态(正在执行某个ForkJoinTask,未阻塞)
3:阻塞状态(正在执行某个ForkJoinTask,但阻塞了,于是调用join(当前线程调用新的线程,并等待新的线程返回值,因为join是等待调用结果的),等待另外一个任务的结果返回,即join实际上相当于get方法的阻塞,只是他是内部的,所以只是他是直接获取而已,而不是我们的手动获取,虽然在前面也有过类似的说明(T类型那个地方)),即有活跃那么自然有阻塞,他们是互相的,这里也说明了如果有多个join,可能前面的阻塞会导致他并不能立即得到结果,所以一般我们最好进行二分,否则效率并不会比单纯的一个线程执行要快很多,虽然也是快的,最差也是相等或者少一丢丢(代码很多),就如单线程是1,那么这里是[0.9,10]这个范围
ctl变量很好地反映出了三种状态:
高32位(没有说明是什么位的,那么代表是前面的32位,而说明的,可以说成两个低32位,前面就有说成"32位中 的两个低16位的地方"):u=(int) (ctl >>> 32),然后u又拆分成tc、ac 两个16位,这里当成高32位的值
低32位:c=(int) ctl,这里当成低32位的值
c>0,说明Treiber Stack不为空,有空闲线程,c=0,说明没有空闲线程
2:ac>0,说明有活跃线程,ac<=0,说明没有空闲线程,并且还未超出parallelism
3:tc>0,说明总线程数 >parallelism
在提交任务的时候:
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
        return externalSubmit(task);
    }

  private <T> ForkJoinTask<T> externalSubmit(ForkJoinTask<T> task) {
        Thread t; ForkJoinWorkerThread w; WorkQueue q;
        if (task == null)
            throw new NullPointerException();
        if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) &&
            (w = (ForkJoinWorkerThread)t).pool == this &&
            (q = w.workQueue) != null)
            q.push(task);
        else
            externalPush(task);
        return task;
    }

 final void externalPush(ForkJoinTask<?> task) {
        int r;                                // initialize caller's probe
        if ((r = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();
            r = ThreadLocalRandom.getProbe();
        }
        for (;;) {
            WorkQueue q;
            int md = mode, n;
            WorkQueue[] ws = workQueues;
            if ((md & SHUTDOWN) != 0 || ws == null || (n = ws.length) <= 0)
                throw new RejectedExecutionException();
            else if ((q = ws[(n - 1) & r & SQMASK]) == null) { // add queue
                int qid = (r | QUIET) & ~(FIFO | OWNED);
                Object lock = workerNamePrefix;
                ForkJoinTask<?>[] qa =
                    new ForkJoinTask<?>[INITIAL_QUEUE_CAPACITY];
                q = new WorkQueue(this, null);
                q.array = qa;
                q.id = qid;
                q.source = QUIET;
                if (lock != null) {     // unless disabled, lock pool to install
                    synchronized (lock) {
                        WorkQueue[] vs; int i, vn;
                        if ((vs = workQueues) != null && (vn = vs.length) > 0 &&
                            vs[i = qid & (vn - 1) & SQMASK] == null)
                            vs[i] = q;  // else another thread already installed
                    }
                }
            }
            else if (!q.tryLockPhase()) // move if busy
                r = ThreadLocalRandom.advanceProbe(r);
            else {
                if (q.lockedPush(task))
                    signalWork(); //这个地方
                return;
            }
        }
    }
在通知工作线程的时候,需要判断ctl的状态,如果没有闲置的线程(一般没有线程也是一样会开启线程),则开启新线程:
 final void signalWork() {
        for (;;) {
            long c; int sp; WorkQueue[] ws; int i; WorkQueue v;
            //这里是主要的,即这就是开启新线程的由来
            if ((c = ctl) >= 0L)                      // enough workers
                break;
            else if ((sp = (int)c) == 0) {            // no idle workers
                if ((c & ADD_WORKER) != 0L)           // too few workers
                    tryAddWorker(c);
                break;
            }
            else if ((ws = workQueues) == null)
                break;                                // unstarted/terminated
            else if (ws.length <= (i = sp & SMASK))
                break;                                // terminated
            else if ((v = ws[i]) == null)
                break;                                // terminating
            else {
                int np = sp & ~UNSIGNALLED;
                int vp = v.phase;
                long nc = (v.stackPred & SP_MASK) | (UC_MASK & (c + RC_UNIT));
                Thread vt = v.owner;
                if (sp == vp && CTL.compareAndSet(this, c, nc)) {
                    v.phase = np;
                    if (vt != null && v.source < 0)
                        LockSupport.unpark(vt);
                    break;
                }
            }
        }
    }
Worker线程的阻塞-唤醒机制:
ForkerJoinPool 没有使用 BlockingQueue,也就不利用其阻塞/唤醒机制来操作线程之间的唤醒或者阻塞,而是利用了park/unpark原语,并 自行实现了Treiber Stack(队列一般还是阻塞队列的)
下面进行详细分析ForkerJoinPool,在阻塞和唤醒的时候,分别是如何入栈的
阻塞–入栈:
当一个线程窃取不到任何任务,也就是处于空闲状态时就会阻塞入栈(之所以要阻塞栈,是要完成线程之间的唤醒,前面说明了阻塞栈的定义,并且也为了唤醒并得到join的对应返回值的成功的操作)
 public void run() {
        if (workQueue.array == null) { // only run once
            Throwable exception = null;
            try {
                onStart();
                pool.runWorker(workQueue); //调用runWorker准备启动线程
            } catch (Throwable ex) {
                exception = ex;
            } finally {
                try {
                    onTermination(exception);
                } catch (Throwable ex) {
                    if (exception == null)
                        exception = ex;
                } finally {
                    pool.deregisterWorker(this, exception);
                }
            }
        }
    }

 final void runWorker(WorkQueue w) {
     // 随机数
        int r = (w.id ^ ThreadLocalRandom.nextSecondarySeed()) | FIFO; // rng
     // 初始化任务数组
        w.array = new ForkJoinTask<?>[INITIAL_QUEUE_CAPACITY]; // initialize
        for (;;) {
            int phase;
            // 扫描是否有需要执行的一个或多个(顶级,即不是子任务,即相当于直接的对应方法的执行)任务
            // 其中包含了窃取的任务执行,以及线程局部队列中任务的执行
            // 如果发现了就执行,返回true
            // 如果获取不到任务,就需要将该线程入队列,阻塞
            if (scan(w, r)) {                     // scan until apparently empty    这个地方
                // 随机数
                r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // move (xorshift)
            }
            // 如果是已经入队列阻塞的,因为phase大于0表示加锁
            else if ((phase = w.phase) >= 0) {    // enqueue, then rescan
                long np = (w.phase = (phase + SS_SEQ) | UNSIGNALLED) & SP_MASK;
                long c, nc;
                do {
                    w.stackPred = (int)(c = ctl);
                    // ForkJoinPool中status表示运行中的线程的,数字减一,因为入队列了
                    nc = ((c - RC_UNIT) & UC_MASK) | np;
                    // CAS操作,自旋,直到操作成功,即保证入队,因为可能有很多人入队,为了保证数据完整,所以需要CAS
                } while (!CTL.weakCompareAndSet(this, c, nc));
            }
            else {                                // already queued
                int pred = w.stackPred;
                Thread.interrupted();             // clear before park
                w.source = DORMANT;               // enable signal
                long c = ctl;
                int md = mode, rc = (md & SMASK) + (int)(c >> RC_SHIFT);
                // 如果ForkJoinPool停⽌,则break,跳出循环
                if (md < 0)                       // terminating
                    break;
                // 优雅关闭
                else if (rc <= 0 && (md & SHUTDOWN) != 0 &&
                         tryTerminate(false, false))
                    break;                        // quiescent shutdown
                else if (rc <= 0 && pred != 0 && phase == (int)c) {
                    long nc = (UC_MASK & (c - TC_UNIT)) | (SP_MASK & pred);
                    long d = keepAlive + System.currentTimeMillis();
                    // 线程阻塞,计时等待
                    LockSupport.parkUntil(this, d);
                    if (ctl == c &&               // drop on timeout if all idle
                        d - System.currentTimeMillis() <= TIMEOUT_SLOP &&
                        CTL.compareAndSet(this, c, nc)) {
                        // 不再扫描,需要入队列
                        w.phase = QUIET;
                        break;
                    }
                }
                // phase为1,表示加锁,phase为负数表示入队列
                else if (w.phase < 0)
                    // 如果phase小于0,表示阻塞,排队中
                    LockSupport.park(this);       // OK if spuriously woken
                w.source = 0;                     // disable signal
            }
        }
    }
// 从一个队列中扫描一个或多个顶级任务,如果有,就执⾏
// 对于⾮空队列,执⾏任务,返回true
private boolean scan(WorkQueue w, int r) {
        WorkQueue[] ws; int n;
    // 如果workQueues不是null,并且workQueue的⻓度大于0,并且w⾮空,w是线程的workQueue
        if ((ws = workQueues) != null && (n = ws.length) > 0 && w != null) {
            // m是ws⻓度减一,获取ws顶部workQueue
            for (int m = n - 1, j = r & m;;) {
                WorkQueue q; int b;
                // 随机获取workQueue,如果该workQueue的顶指针和底指针不相等,表示有需要执⾏的任务
                if ((q = ws[j]) != null && q.top != (b = q.base)) {
                    int qid = q.id;
                    ForkJoinTask<?>[] a; int cap, k; ForkJoinTask<?> t;
                    // 如果workQueue的任务队列不是null,并且元素⾮空	
                    if ((a = q.array) != null && (cap = a.length) > 0) {
                        // 获取队列顶部任务
                        t = (ForkJoinTask<?>)QA.getAcquire(a, k = (cap - 1) & b);
                        // 如果q的base值没有被别的线程修改过,t不是null,并且将t从数组中移除成功
                        // 即可在当前⼯作线程执⾏该任务	
                        if (q.base == b++ && t != null &&
                            QA.compareAndSet(a, k, t, null)) {
                            // base+1
                            q.base = b;
                            // 更改source为当前id
                            w.source = qid;
                            // 如果还有任务需要执⾏,通知其他闲置的线程执⾏
                            if (q.top - b > 0)
                                signalWork();
                            // 让workQueue中的⼯作线程来执⾏不管是窃取来的,还是本地的任务,还是从queue中获取的其他任务
                            // 公平起⻅,添加一个随机的边界;剩下的让别的线程来执⾏
                            w.topLevelExec(t, q,  // random fairness bound
                                           r & ((n << TOP_BOUND_SHIFT) - 1));
                        }
                    }
                    return true;
                }
                else if (--n > 0)
                    j = (j + 1) & m;
                else
                    break;
            }
        }
        return false;
    }

唤醒–出栈:
在新的任务到来之后,空闲的线程被唤醒,其核⼼逻辑在signalWork方法里面
//前面说明过了,即开启线程的地方 
final void signalWork() {
        for (;;) {
            long c; int sp; WorkQueue[] ws; int i; WorkQueue v;
            // ⾜够的worker线程
            if ((c = ctl) >= 0L)                      // enough workers
                break;
            // 没有闲置的worker线程
            else if ((sp = (int)c) == 0) {            // no idle workers
                // worker线程太少
                if ((c & ADD_WORKER) != 0L)           // too few workers
                    tryAddWorker(c); // 尝试添加新的worker线程
                break;
            }
            else if ((ws = workQueues) == null)
                // 线程池没有启动或已经停⽌了
                break;                                // unstarted/terminated
            else if (ws.length <= (i = sp & SMASK))
                // 线程池停⽌了
                break;                                // terminated
            else if ((v = ws[i]) == null)
                // 线程池正在停⽌中,对应的ForkJoinPool也看成是线程池,所以上面相关的都可以认为是线程池的操作
                break;                                // terminating
            else {
                int np = sp & ~UNSIGNALLED;
                int vp = v.phase;
                long nc = (v.stackPred & SP_MASK) | (UC_MASK & (c + RC_UNIT));
                Thread vt = v.owner;
                if (sp == vp && CTL.compareAndSet(this, c, nc)) {
                    v.phase = np;
                    // 如果栈顶元素存在,并且满足如下
                    if (vt != null && v.source < 0)
                        // 那么唤醒线程vt(上面是无限循环哦),所以在得到任务时,必然是提交任务,也就会到这里来,你得到后,是没有添加线程,说明有空闲线程(很明显,这里的阻塞状态也算空闲的,因为他只是得到结果,即也归属于这里)
                        LockSupport.unpark(vt);
                    break;
                }
            }
        }
    }

任务的提交过程分析:
在明⽩了工作窃取队列、ctl变量的各种状态、Worker的各种状态,以及线程阻塞—唤醒机制之后,接下来综 合这些知识,详细分析任务的提交和执行过程
关于任务的提交,ForkJoinPool最外层的接口如下所示
 public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
        return externalSubmit(task);
    }

//将一个可能是外部任务的子任务入队列
  private <T> ForkJoinTask<T> externalSubmit(ForkJoinTask<T> task) {
        Thread t; ForkJoinWorkerThread w; WorkQueue q;
      // 任务为null,抛异常
        if (task == null)
            throw new NullPointerException();
      // 如果当前线程是ForkJoinWorkerThread类型的,并且该线程的pool就是当前对象
      // 并且当前pool的workQueue不是null,则将当前任务入队列
      //instanceof看看左边是否是你的实例,也就是说右边是我的类或者父类才可是true(注意:静态内部类若要继承自身的内部类,那么只能是静态的内部类,这是对内部类来说的,因为内部类的静态和不是静态的创建对象是不同的,所以防止对应的继承问题,需要一致类型才可,这是规定,所以了解即可)
        if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) &&
            (w = (ForkJoinWorkerThread)t).pool == this &&
            (q = w.workQueue) != null)
            // 当前任务入队,即局部队列
            q.push(task);
        else
            // 否则该任务不是当前线程的子任务,调用外部入队方法,加入全局队列
            externalPush(task);
        return task;
    }

如何区分一个任务是内部任务,还是外部任务呢?
可以通过调用该方法的线程类型判断
如果线程类型是ForkJoinWorkerThread,说明是线程池内部的某个线程在调用该方法,则把该任务放入该线 程的局部队列(简单来说就是由线程开启的线程才会放入局部,而这样开启的线程,是ForkJoinWorkerThread类型的,而不是我们的Thread类型,虽然ForkJoinWorkerThread继承Thread
否则,是外部线程在调用该方法,则将该任务加入全局队列,注意,我们说的阻塞队列是对线程的任务保存的,而线程本身是自己保存的,我们只需要拿取即可,就如连接池一样,之所以可以空闲线程入栈,是因为除了在运行中可以被赋值外(他只是针对线程来说的,赋值不会改变线程),更重要的是入栈可以操作唤醒(可以赋值,但是为了保证可以操作返回值,我们需要对线程之间进行唤醒,而不是任务唤醒线程,即需要多个(另外一个)唤醒操作了)
这里也要注意:队列是有顺序的,也就是说任务是有顺序的给,只是线程多了而已,虽然线程有线程集合,但是他们之间是抢占,虽然并没有具体说明,这里了解即可
内部提交任务push:
内部提交任务,即上面的q.push(task),会放入该线程的工作窃取队列中,代码如下所示
  final void push(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a;
            int s = top, d, cap, m;
            ForkJoinPool p = pool;
            if ((a = array) != null && (cap = a.length) > 0) {
                QA.setRelease(a, (m = cap - 1) & s, task);
                top = s + 1; //由于是单线程,不需要加锁,直接累加top即可,因为局部执行,也的确只有一个线程来执行的,执行完才可以操作全局,虽然说对应的fork是开启线程,但是只是对没有线程之前来说的,他在开启时,自然有局部队列任务给他操作,是先开启,然后队列,然后线程执行(始终判断是否有,就如空闲一样的判断)
                if (((d = s - (int)BASE.getAcquire(this)) & ~1) == 0 &&
                    p != null) {                 // size 0 or 1
                    VarHandle.fullFence();
                    p.signalWork(); //通知空闲线程,即线程与线程的通知(操作返回值的),他们是互相的
                }
                else if (d == m)
                    growArray(false); //队列扩容
            }
        }
由于工作窃取队列的特性,操作是单线程的,所以此处不需要执行CAS操作
外部提交任务:
final void externalPush(ForkJoinTask<?> task) {
        int r;                                // initialize caller's probe
    // ⽣成随机数
        if ((r = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();
            r = ThreadLocalRandom.getProbe();
        }
        for (;;) {
            WorkQueue q;
            int md = mode, n;
            WorkQueue[] ws = workQueues;
            // 如果ForkJoinPool关闭,或者任务队列是null,或者ws的长度小于等于0,拒收任务
            if ((md & SHUTDOWN) != 0 || ws == null || (n = ws.length) <= 0)
                throw new RejectedExecutionException();
            // 如果随机数计算的workQueues索引处的元素为null,则添加队列
            // 即提交任务的时候,是随机向workQueue中添加workQueue,负载均衡的考虑
            else if ((q = ws[(n - 1) & r & SQMASK]) == null) { // add queue
                // 计算新workQueue对象的id值
                int qid = (r | QUIET) & ~(FIFO | OWNED);
                // worker线程名称前缀
                Object lock = workerNamePrefix;
                // 创建任务数组
                ForkJoinTask<?>[] qa =
                    new ForkJoinTask<?>[INITIAL_QUEUE_CAPACITY];
                // 创建WorkQueue,将当前线程作为
                q = new WorkQueue(this, null);
                // 将任务数组赋值给workQueue
                q.array = qa;
                // 设置workQueue的id值
                q.id = qid;
                // 由于是通过客户端线程添加的workQueue,没有前置workQueue
                // 内部提交任务有源workQueue,表示子任务
                q.source = QUIET;
                if (lock != null) {     // unless disabled, lock pool to install
                    synchronized (lock) {
                        WorkQueue[] vs; int i, vn;
                        // 如果workQueues数组不是null,其中有元素
                        // 并且qid对应的workQueues中的元素为null,则赋值
                        // 因为有可能其他线程将qid对应的workQueues处的元素设置了
                        // 所以需要加锁,并判断元素是否为null
                        if ((vs = workQueues) != null && (vn = vs.length) > 0 &&
                            vs[i = qid & (vn - 1) & SQMASK] == null)
                            vs[i] = q;  // else another thread already installed
                    }
                }
            }
            // CAS操作,使用随机数
            else if (!q.tryLockPhase()) // move if busy
                r = ThreadLocalRandom.advanceProbe(r);
            else {
                // 如果任务添加成功,通知线程池调度,执行
                if (q.lockedPush(task)) //这个地方
                    signalWork();
                return;
            }
        }
    }
lockedPush(task)方法的实现:
 final boolean lockedPush(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a;
            boolean signal = false;
            int s = top, b = base, cap, d;
            if ((a = array) != null && (cap = a.length) > 0) {
                a[(cap - 1) & s] = task;
                top = s + 1;
                if (b - s + cap - 1 == 0)
                    growArray(true);
                else {
                    phase = 0; // full volatile unlock
                    if (((s - base) & ~1) == 0) // size 0 or 1
                        signal = true;
                }
            }
            return signal;
        }
外部多个线程会调用该方法,所以要加锁,入队列和扩容的逻辑和线程内部的队列基本相同,最后,调用signalWork(),通知一个空闲线程来取
工作窃取算法:任务的执行过程分析:
全局队列有任务,局部队列也有任务,每一个Worker线程都会不间断地扫描这些队列,窃取任务来执行(也就是说局部队列也会进行窃取,但是可能也只是由对应的一个线程执行完毕,但是一般我认为是窃取的),下 面从Worker线程的run方法开始分析:
public void run() {
        if (workQueue.array == null) { // only run once
            Throwable exception = null;
            try {
                onStart();
                pool.runWorker(workQueue); //调用该方法
            } catch (Throwable ex) {
                exception = ex;
            } finally {
                try {
                    onTermination(exception);
                } catch (Throwable ex) {
                    if (exception == null)
                        exception = ex;
                } finally {
                    pool.deregisterWorker(this, exception);
                }
            }
        }
    }
run()方法调用的是所在ForkJoinPool的runWorker方法,如下所示
 final void runWorker(WorkQueue w) {
        int r = (w.id ^ ThreadLocalRandom.nextSecondarySeed()) | FIFO; // rng
        w.array = new ForkJoinTask<?>[INITIAL_QUEUE_CAPACITY]; // initialize
        for (;;) {
            int phase;
            if (scan(w, r)) {                     // scan until apparently empty
                r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // move (xorshift)
            }
            else if ((phase = w.phase) >= 0) {    // enqueue, then rescan
                long np = (w.phase = (phase + SS_SEQ) | UNSIGNALLED) & SP_MASK;
                long c, nc;
                do {
                    w.stackPred = (int)(c = ctl);
                    nc = ((c - RC_UNIT) & UC_MASK) | np;
                } while (!CTL.weakCompareAndSet(this, c, nc));
            }
            else {                                // already queued
                int pred = w.stackPred;
                Thread.interrupted();             // clear before park
                w.source = DORMANT;               // enable signal
                long c = ctl;
                int md = mode, rc = (md & SMASK) + (int)(c >> RC_SHIFT);
                if (md < 0)                       // terminating
                    break;
                else if (rc <= 0 && (md & SHUTDOWN) != 0 &&
                         tryTerminate(false, false))
                    break;                        // quiescent shutdown
                else if (rc <= 0 && pred != 0 && phase == (int)c) {
                    long nc = (UC_MASK & (c - TC_UNIT)) | (SP_MASK & pred);
                    long d = keepAlive + System.currentTimeMillis();
                    LockSupport.parkUntil(this, d);
                    if (ctl == c &&               // drop on timeout if all idle
                        d - System.currentTimeMillis() <= TIMEOUT_SLOP &&
                        CTL.compareAndSet(this, c, nc)) {
                        w.phase = QUIET;
                        break;
                    }
                }
                else if (w.phase < 0)
                    LockSupport.park(this);       // OK if spuriously woken
                w.source = 0;                     // disable signal
            }
        }
    }
下面详细看扫描过程scan(w, a):
 private boolean scan(WorkQueue w, int r) {
        WorkQueue[] ws; int n;
        if ((ws = workQueues) != null && (n = ws.length) > 0 && w != null) {
            for (int m = n - 1, j = r & m;;) {
                WorkQueue q; int b;
                if ((q = ws[j]) != null && q.top != (b = q.base)) {
                    int qid = q.id;
                    ForkJoinTask<?>[] a; int cap, k; ForkJoinTask<?> t;
                    if ((a = q.array) != null && (cap = a.length) > 0) {
                        t = (ForkJoinTask<?>)QA.getAcquire(a, k = (cap - 1) & b);
                        if (q.base == b++ && t != null &&
                            QA.compareAndSet(a, k, t, null)) {
                            q.base = b;
                            w.source = qid;
                            if (q.top - b > 0)
                                signalWork();
                            w.topLevelExec(t, q,  // random fairness bound
                                           r & ((n << TOP_BOUND_SHIFT) - 1));
                        }
                    }
                    return true;
                }
                else if (--n > 0)
                    j = (j + 1) & m;
                else
                    break;
            }
        }
        return false;
    }

对应的方法前面说明过了,且看起来也去局部窃取了,即顶级包含所有
ForkJoinTask的fork/join:
如果局部队列、全局中的任务全部是相互独立的,就很简单了,但问题是,对于分治算法来说,分解出来的一 个个任务并不是独立的,而是相互依赖,一个任务的完成要依赖另一个前置任务的完成
这种依赖关系是通过ForkJoinTask中的join()来体现的,且看前面的代码:
protected void compute() {
 if (lo < hi) {
 // 分区
 int pivot = partition(array, lo, hi);
 SortTask left = new SortTask(array, lo, pivot - 1);
 SortTask right = new SortTask(array, pivot + 1, hi);
 left.fork();
 right.fork();
 left.join();
 right.join();
 }
}
线程在执行当前ForkJoinTask的时候,产⽣了left、right 两个子Task
fork是指把这两个子Task放入队列里面,也就是任务放在队列使得执行(局部队列,并且是对应的线程来放的,开启线程,有队列,然后执行,前面说明过大体意思"是先开启,然后队列,然后线程执行")
join则是要等待2个子Task完成
而子Task在执行过程中,会再次产⽣两个子Task,如此层层嵌套,类似于递归调用,直到最底层的Task计算 完成,再一级级返回
fork:
fork()的代码很简单,就是把自己放入当前线程所在的局部队列中
如果是外部线程调用fork方法,则直接将任务添加到共享队列中
  public final ForkJoinTask<V> fork() {
        Thread t;
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            ForkJoinPool.common.externalPush(this);
        return this;
    }
join的嵌套:
join的层层嵌套阻塞原理:
join会导致线程的层层嵌套阻塞,如图所示:

在这里插入图片描述

线程1在执行 ForkJoinTask1,在执行过程中调用了 forkJoinTask2.join(),所以要等ForkJoinTask2完成,线程1才能返回
线程2在执行ForkJoinTask2,但由于调用了forkJoinTask3.join(),只有等ForkJoinTask3完成后,线程2才能返 回
线程3在执行ForkJoinTask3
结果是:线程3⾸先执行完,然后线程2才能执行完,最后线程1再执行完,所有的任务其实组成一个有向无环 图DAG,如果线程3调用了forkJoinTask1.join(),那么会形成环,造成死锁,相当于在线程3中操作无限循环(在代码中可以选择保存一个join的调用者,在一定的情况下,突然执行就行了,当然,无论是操作返回值的类型还是默认返回null,都会阻塞,只不过没有操作返回值的没有设置返回值而已,相当于他们需要通知,即这个阻塞是操作他们的,即RecursiveTask和RecursiveAction的)
那么,这种层次依赖、层次通知的 DAG,在 ForkJoinTask 内部是如何实现的呢?站在ForkJoinTask的角度来 看,每个ForkJoinTask,都可能有多个线程在等待它完成,有1个线程在执行它,所以每个ForkJoinTask就是一个 同步对象,线程在调用join()的时候,阻塞在这个同步对象上面,执行完成之后,再通过这个同步对象通知所有等 待的线程(虽然一般只是直接的一个线程,但是总体来说是后面的所有),实际上在main方法中操作多线程,只是多出一个线程池而已,即不会影响的
利用synchronized关键字和Java原⽣的wait()/notify()机制,实现了线程的等待-唤醒机制,调用join()的这些线 程,内部其实是调用ForkJoinTask这个对象的wait(),执行该任务的Worker线程,在任务执行完毕之后,顺便调用notifyAll()

在这里插入图片描述

那么可以知道,如果在其他的线程中也操作了join(多个线程中存在join),那么一起唤醒,虽然在案例中没有给出测试(可以在对应的方法里面,手动开一个线程,执行join就知道了)
ForkJoinTask的状态解析:
要实现fork()/join()的这种线程间的同步,对应的ForkJoinTask一定是有各种状态的,这个状态变量是实现fork/join的基础
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
 // ...

 // 由ForkJoinPool和workers直接调用

 // 需要是volatile的

 volatile int status;
 private static final int DONE = 1 << 31; // 负值

 private static final int ABNORMAL = 1 << 18; // 设置DONE的时候自动设置

 private static final int THROWN = 1 << 17; // 设置ABNORMAL的时候自动设置

 private static final int SIGNAL = 1 << 16; // 如果在调用join的线程正在等待,则为true

 private static final int SMASK = 0xffff; // short bits for tags

 // ...

}
初始时,status=0,共有五种状态,可以分为两大类:
1:未完成:status>=0
2:已完成:status<0
所以,通过判断是status>=0,还是status<0,就可知道任务是否完成,进而决定调用join()的线程是否需要 被阻塞
join的详细实现:
 public final V join() {
        int s;
        if (((s = doJoin()) & ABNORMAL) != 0) //非正常结束,抛异常
            reportException(s);
        return getRawResult(); //正常结束返回结果
    }
getRawResult()是ForkJoinTask中的一个模板方法,分别被RecursiveAction和RecursiveTask实现,前者没有 返回值,所以返回null,后者返回一个类型为V的result变量,看对应的RecursiveAction或者RecursiveTask里面的方法就知道了,虽然对应的RecursiveAction泛型默认是Void类型,而由于RecursiveAction有默认返回,所以不用设置,他们都操作阻塞,所以在对应的方法中,可以看到join方法的内容基本是一样的,只是在内部对应的RecursiveAction可能并没有进行赋值,而RecursiveTask可能操作了赋值(可能按照类型来的,这里了解即可,因为没有具体说明)
阻塞主要发⽣在上面的doJoin()方法里面,在dojoin()里调用t.join()的线程会阻塞
然后等待任务t执行完成, 再唤醒该阻塞线程,doJoin()返回,使得最终返回结果(会看看是否正常结束)
注意:当 doJoin()返回的时候,就是该任务执行完成的时候,doJoin()的返回值就是任务的完成状态,也就是 上面的⼏种状态
 private int doJoin() {
        int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
     // 如果status < 0表示任务已经完成,不⽤阻塞,直接返回
        return (s = status) < 0 ? s :
     // 否则判断线程是否是⼯作线程
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
            (w = (wt = (ForkJoinWorkerThread)t).workQueue).
            tryUnpush(this) && (s = doExec()) < 0 ? s :
            wt.pool.awaitJoin(w, this, 0L) :
            externalAwaitDone();
    }

上面的返回值可读性比较差,变形之后:
// 如果status < 0,直接返回s值
if ((s = status) < 0) {
 return s;
} else {
 // 如果线程是⼯作线程
 if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
 // 将任务能够从局部队列弹出,并调⽤doExec()⽅法执⾏成功
 if (w = (wt = (ForkJoinWorkerThread)t).workQueue).tryUnpush(this) && (s = doExec()) < 0) {
 // 返回s值
 return s;
 } else {
 // 否则等待,线程阻塞
 wt.pool.awaitJoin(w, this, 0L)
 }
 } else {
 // 如果线程不是⼯作线程,则外部等待任务完成,线程阻塞
 externalAwaitDone();
 }
}
先看一下externalAwaitDone(),即外部线程的阻塞过程,相对简单
 private int externalAwaitDone() {
        int s = tryExternalHelp();
        if (s >= 0 && (s = (int)STATUS.getAndBitwiseOr(this, SIGNAL)) >= 0) {
            boolean interrupted = false;
            synchronized (this) {
                for (;;) {
                    if ((s = status) >= 0) {
                        try {
                            // 如果任务还没有完成,阻塞
                            wait(0L);
                        } catch (InterruptedException ie) {
                            interrupted = true;
                        }
                    }
                    else {
                        // 唤醒线程,开始执行
                        notifyAll();
                        break;
                    }
                }
            }
            if (interrupted)
                Thread.currentThread().interrupt();
        }
        return s;
    }

内部Worker线程的阻塞,即上面的wt.pool.awaitJoin(w, this, 0L),相比外部线程的阻塞要做更多工作(因为对应的线程类型与外部的是不一样的,自然需要考虑该类型的一些相关的问题,就如窃取作用,也只能是窃取他们之间的队列,而不是全局队列,全局队列由他们自身来窃取的,即使得全局不窃取局部,只是他们之间的关系,并且局部有很多,所以比较复杂),它现 不在ForkJoinTask里面,而是在ForkJoinWorkerThread里面
 final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
        int s = 0;
        int seed = ThreadLocalRandom.nextSecondarySeed();
        if (w != null && task != null &&
            (!(task instanceof CountedCompleter) ||
             (s = w.helpCC((CountedCompleter<?>)task, 0, false)) >= 0)) {
            // 尝试执行该任务
            w.tryRemoveAndExec(task);
            int src = w.source, id = w.id;
            int r = (seed >>> 16) | 1, step = (seed & ~1) | 2;
            s = task.status;
            while (s >= 0) {
                WorkQueue[] ws;
                int n = (ws = workQueues) == null ? 0 : ws.length, m = n - 1;
                while (n > 0) {
                    WorkQueue q; int b;
                    if ((q = ws[r & m]) != null && q.source == id &&
                        q.top != (b = q.base)) {
                        ForkJoinTask<?>[] a; int cap, k;
                        int qid = q.id;
                        if ((a = q.array) != null && (cap = a.length) > 0) {
                            ForkJoinTask<?> t = (ForkJoinTask<?>)
                                QA.getAcquire(a, k = (cap - 1) & b);
                            if (q.source == id && q.base == b++ &&
                                t != null && QA.compareAndSet(a, k, t, null)) {
                                q.base = b;
                                w.source = qid;
                                // 执行该任务
                                t.doExec();
                                w.source = src;
                            }
                        }
                        break;
                    }
                    else {
                        r += step;
                        --n;
                    }
                }
                // 如果任务的status < 0,任务执行完成,则退出循环,返回s的值
                if ((s = task.status) < 0)
                    break;
                else if (n == 0) { // empty scan
                    long ms, ns; int block;
                    if (deadline == 0L)
                        ms = 0L;                       // untimed
                    else if ((ns = deadline - System.nanoTime()) <= 0L)
                        break;                         // timeout
                    else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
                        ms = 1L;                       // avoid 0 for timed wait
                    if ((block = tryCompensate(w)) != 0) {
                        task.internalWait(ms); //这个地方
                        CTL.getAndAdd(this, (block > 0) ? RC_UNIT : 0L);
                    }
                    s = task.status;
                }
            }
        }
        return s;
    }
上面的方法有个关键点:里面的循环通常是死循环,并且只有一个返回点,即只有在task.status<0,任务完成之后才 可能返回,否则一般会不断自旋,若自旋之后还不行,就会调用task.internalWait(ms);阻塞
task.internalWait(ms);的代码如下:
 final void internalWait(long timeout) {
        if ((int)STATUS.getAndBitwiseOr(this, SIGNAL) >= 0) {
            synchronized (this) {
                if (status >= 0)
                    try { wait(timeout); } catch (InterruptedException ie) { }
                else
                    notifyAll();
            }
        }
    }

join的唤醒:
调用t.join()之后,线程会被阻塞,接下来看另外一个线程在任务t执行完毕后如何唤醒阻塞的线程
//前面注释里面的执行该任务 
final int doExec() {
        int s; boolean completed;
        if ((s = status) >= 0) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                completed = false;
                s = setExceptionalCompletion(rex);
            }
            if (completed) //任务执行完更新STATUS(status),同时通知其他阻塞的线程
                s = setDone();
        }
        return s;
    }

private int setDone() {
        int s;
        if (((s = (int)STATUS.getAndBitwiseOr(this, DONE)) & SIGNAL) != 0)
            synchronized (this) { notifyAll(); } //通知所有被阻塞的线程
        return s | DONE;
    }
任务的执行发⽣在doExec()方法里面,任务执行完成后,调用一个setDone()通知所有等待的线程,这里也做 了两件事:
1:把status置为完成状态
2:一般来说如果s != 0(一般s,即status是正整数,当然他也包括0),即 s = SIGNAL,说明有线程正在等待这个任务执行完成,调用Java原⽣的notifyAll()通知所有 线程,如果s = 0,说明没有线程等待这个任务,不需要通知
ForkJoinPool的优雅关闭:
同ThreadPoolExecutor一样,ForkJoinPool的关闭也不可能是"瞬时的",也是需要一个平滑的过渡过程
工作线程的退出:
对于一个Worker线程来说(线程池基本都有这样),它会在一个for循环里面不断轮询队列中的任务,如果有任务,则执行,处在活跃 状态,如果没有任务,则进入空闲等待状态
这个线程如何退出呢?
//前面说明过了,这里注意看如下解释: 
final void runWorker(WorkQueue w) {
        int r = (w.id ^ ThreadLocalRandom.nextSecondarySeed()) | FIFO; // rng
        w.array = new ForkJoinTask<?>[INITIAL_QUEUE_CAPACITY]; // initialize
        for (;;) {
            int phase;
            if (scan(w, r)) {                     // scan until apparently empty
                r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // move (xorshift)
            }
            else if ((phase = w.phase) >= 0) {    // enqueue, then rescan
                long np = (w.phase = (phase + SS_SEQ) | UNSIGNALLED) & SP_MASK;
                long c, nc;
                do {
                    w.stackPred = (int)(c = ctl);
                    nc = ((c - RC_UNIT) & UC_MASK) | np;
                } while (!CTL.weakCompareAndSet(this, c, nc));
            }
            else {                                // already queued
                int pred = w.stackPred;
                Thread.interrupted();             // clear before park
                w.source = DORMANT;               // enable signal
                long c = ctl;
                int md = mode, rc = (md & SMASK) + (int)(c >> RC_SHIFT);
                if (md < 0)                       // terminating
                    break;
                else if (rc <= 0 && (md & SHUTDOWN) != 0 &&
                         tryTerminate(false, false))
                    break;                        // quiescent shutdown
                // 优雅退出(前面说明了是优雅关闭,优雅代表:很好的意思,即很完美)
                else if (rc <= 0 && pred != 0 && phase == (int)c) {
                    long nc = (UC_MASK & (c - TC_UNIT)) | (SP_MASK & pred);
                    long d = keepAlive + System.currentTimeMillis();
                    LockSupport.parkUntil(this, d);
                    if (ctl == c &&               // drop on timeout if all idle
                        d - System.currentTimeMillis() <= TIMEOUT_SLOP &&
                        CTL.compareAndSet(this, c, nc)) {
                        w.phase = QUIET;
                        break;
                    }
                }
                else if (w.phase < 0)
                    LockSupport.park(this);       // OK if spuriously woken
                w.source = 0;                     // disable signal
            }
        }
    }
一般来说(int) (c = ctl) < 0,即最终导致的原因会是低32位的最高位为1导致的,具体为什么并没有具体说明(这里给出前面的一个说明:ST:1个比特位,如果是1,表示整个ForkJoinPool正在关闭),说明线程池已经进入了关闭状态,但线程池进入关闭状态,不代表 所有的线程都会立⻢关闭
对应的ForkJoinPool类的shutdown()与shutdownNow()的区别:
public void shutdown() {
 checkPermission();
 tryTerminate(false, true);
}
 

public List<Runnable> shutdownNow() {
 checkPermission();
 tryTerminate(true, true);
 return Collections.emptyList();
}
二者的代码基本相同,都是调用tryTerminate(boolean, boolean)方法,其中一个传入的是false,另一个传入 的是true,tryTerminate意为试图关闭ForkJoinPool,但并不保证一定可以关闭成功(所以有循环,实际上就算是循环可能也关闭不了,但是一般不会,否则是方法的问题,那么自然也不会直接的给我们使用的)
//也是对应的ForkJoinPool,也不是ThreadPoolExecutor,只是名称相同而已,因为我们也将ForkJoinPool看成线程池
private boolean tryTerminate(boolean now, boolean enable) {
    // 三个阶段:尝试设置为SHUTDOWN,之后STOP,最后TERMINATED(停止,关闭),虽然这些标志并没有具体说明
        int md; // 3 phases: try to set SHUTDOWN, then STOP, then TERMINATED

        while (((md = mode) & SHUTDOWN) == 0) {
            if (!enable || this == common)        // cannot shutdown
                return false;
            else
                // 将mode变量CAS操作设置为SHUTDOWN
                MODE.compareAndSet(this, md, md | SHUTDOWN);
        }

        while (((md = mode) & STOP) == 0) {       // try to initiate termination
            if (!now) {                           // check if quiescent & empty
                for (long oldSum = 0L;;) {        // repeat until stable
                    boolean running = false;
                    long checkSum = ctl;
                    WorkQueue[] ws = workQueues;
                    if ((md & SMASK) + (int)(checkSum >> RC_SHIFT) > 0)
                        // 还有正在运行的线程	
                        running = true;
                    else if (ws != null) {
                        WorkQueue w;
                        for (int i = 0; i < ws.length; ++i) {
                            if ((w = ws[i]) != null) {
                                int s = w.source, p = w.phase;
                                int d = w.id, b = w.base;
                                if (b != w.top ||
                                    ((d & 1) == 1 && (s >= 0 || p >= 0))) {
                                    running = true;
                                    // 还正在运行
                                    break;     // working, scanning, or have work
                                }
                                checkSum += (((long)s << 48) + ((long)p << 32) +
                                             ((long)b << 16) + (long)d);
                            }
                        }
                    }
                    if (((md = mode) & STOP) != 0)
                        break;                 // already triggered
                    else if (running)
                        return false;
                    else if (workQueues == ws && oldSum == (oldSum = checkSum))
                        break;
                }
            }
            if ((md & STOP) == 0)
                //一般shutdownNow会直接到这里,因为if (!now) { 结果是false
                // 如果需要立即停⽌,同时md没有设置为STOP,则设置为STOP
                MODE.compareAndSet(this, md, md | STOP);
        }

    // 如果mode还没有设置为TERMINATED,则进行循环
        while (((md = mode) & TERMINATED) == 0) { // help terminate others
            for (long oldSum = 0L;;) {            // repeat until stable
                WorkQueue[] ws; WorkQueue w;
                long checkSum = ctl;
                if ((ws = workQueues) != null) {
                    for (int i = 0; i < ws.length; ++i) {
                        if ((w = ws[i]) != null) {
                            ForkJoinWorkerThread wt = w.owner;
                            // 清空任务队列
                            w.cancelAll();        // clear queues
                            if (wt != null) {
                                try {             // unblock join or park
                                    // 中断join或park的线程
                                    wt.interrupt();
                                } catch (Throwable ignore) {
                                }
                            }
                            checkSum += ((long)w.phase << 32) + w.base;
                        }
                    }
                }
                // 如果已经设置了TERMINATED,则跳出for循环,while循环条件为false,整个方法返回true,停⽌
                if (((md = mode) & TERMINATED) != 0 ||
                    (workQueues == ws && oldSum == (oldSum = checkSum)))
                    break;
            }
            if ((md & TERMINATED) != 0)
                break;
            else if ((md & SMASK) + (short)(ctl >>> TC_SHIFT) > 0)
                break;
            else if (MODE.compareAndSet(this, md, md | TERMINATED)) {
                synchronized (this) {
                    // 通知调用awaitTermination的线程,关闭ForkJoinPool了
                    notifyAll();                  // for awaitTermination
                }
                break;
            }
        }
        return true;
    }

线程在执行时,对应的方法是给出一份的(一般是方法区给的),也就是说,方法可以多人进入,而对于变量,但是如果是局部的,而不是堆(使用同一个对象)或者方法区(静态)的,那么一般是不共享的
总结:shutdown()只拒绝新提交的任务(对应的if (!now) { 是true,因为 tryTerminate(false, true);是shutdown方法的),shutdownNow()会取消现有的全局队列和局部队列中的任务,同时 唤醒所有空闲的线程,让这些线程自动退出(对应的if (!now) { 是false,因为tryTerminate(true, true);是shutdownNow方法的)
至此线程池的一些知识大致说明完毕
多线程设计模式(这里最好了解,大多数的JUC里面的类或者接口,一般多是类,都使用到了多线程设计模式,比如后面我们手写的读写锁就满足了对应的模式,并在相应的后面,也会给出JUC里面的类似使用该模式的类例子,如ReentrantReadWriteLock类):
Single Threaded Execution(英文意思:单线程执行)模式:
所谓Single Threaded Execution模式,指的是"以一个线程执行",该模式用于设置限制,以确保同一时间只能 让一个线程执行处理
Single Threaded Execution有时也称为临界区(critical section)或临界域(critical region),Single Threaded Execution名称侧重于执行处理的线程(一个线程),而别称临界区或临界域侧重于执行范围,即执行安全,简单来说就是在某个地方进行单线程执行,如后面加锁的方法
示例程序:
package main8;

/**
 *
 */
public class NumberResource {
    private Integer number = 0;
    private Integer printIndex = 0;

    private void add() {
        this.number++;
    }

    private Integer get() {
        return this.number;
    }

    public void showNumber() throws InterruptedException {
        // 大概打印100次,退出虚拟机
        if (printIndex > 100) System.exit(0); //这里一般会执行两次,因为还没有来得及关闭,他也进行执行了,虽然并不会在内部继续执行,但是如果这个代码上面有打印,那么会打印两次
        Integer number1 = this.get();
        this.add();
        Thread.sleep(5);
        Integer number2 = this.get();
        if ((number1 + 1) == number2) {
            System.out.println(Thread.currentThread().getName() + " => 递增--正确- -:" + number1 + " ***** " + number2);
        } else {
            System.out.println(Thread.currentThread().getName() + " => 递增**异常 **:" + number1 + " ***** " + number2);
        }
        // 增加计数
        printIndex++;
    }
}

package main8;

/**
 *
 */
public class UserThread extends Thread {
    private NumberResource resource;

    public UserThread(NumberResource resource) {
        this.resource = resource;
    }

    @Override

    public void run() {
        while (true) {
            try {
                resource.showNumber();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package main8;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) {
        NumberResource resource = new NumberResource();
        new UserThread(resource).start();
        new UserThread(resource).start();
    }
}

上述代码会出现递增异常,之所以递增异常,是因为showNumber方法是一个临界区,其中对数字加一,但又不能保证原子 性(你加我也加,那么自然递增异常),在多线程执行的时候,就会出现问题
线程安全的NumberResource类:
    public synchronized void showNumber() throws InterruptedException { //加上synchronized即可
Single Threaded Execution模式总结:
SharedResource(共享资源):
Single Threaded Execution模式中出现了一个发挥SharedResource(共享资源)作用的类,在示例程序中, 由NumberResource类扮演SharedResource角色,如操作里面的对应的number变量和printIndex变量,虽然他一般代表操作锁的共享资源
SharedResource角色是可以被多个线程访问的类,包含很多方法,但这些方法主要分为如下两类:
safeMethod(英文意思:安全方法):多个线程同时调用也不会发⽣问题的方法(如上面加上了synchronized的方法)
unsafeMethod(英文意思:不安全方法):多个线程同时访问会发⽣问题,因此必须加以保护的方法(如上面没有加上synchronized的方法)
safeMethod,无需考虑分歧或者什么问题
而对于unsafeMethod,在被多个线程同时执行时,实例状态有可能发⽣分歧,这时就需要保护该方法,使其不 被多个线程同时访问
Single Threaded Execution模式会保护unsafeMethod,使其同时只能由一个线程访问,一般java通常是通过unsafeMethod声明为synchronized方法来进行保护(使得变成安全方法),上面也是这样做的
我们将只允许单个线程执行的程序范围称为临界区
Single Threaded Execution类图:

在这里插入图片描述

上面的指向只是一个名称,代表使得可以是单个线程的操作
何时使用Single Threaded Execution模式:
1:多线程时
在单线程程序中使用synchronized关键字并不会破坏程序的安全性,但是调用synchronized方法要比调用一 般方法花费时间,稍微降低程序性能
2:多个线程访问时(可以认为"多线程时"包括"多个线程访问")
当SharedResource角色的实例有可能被多个线程同时访问时,就需要使用Single Threaded Execution模式
即便是多线程程序,如果所有线程都是完全独立操作的(安全或者分开),也无需使用Single Threaded Execution模式,这种 状态称为线程互不⼲涉
在某些处理多个线程的框架中,有时线程的独立性是由框架控制的(具体可以自己百度,比如线程安全的集合等待,安全,或者线程池,分开),此时,框架的使用者就无需考虑是否使用Single Threaded Execution模式
3:状态有可能变化时
之所以需要使用Single Threaded Execution模式,是因为SharedResource角色的状态会发⽣变化(整个状态,我们也可以认为是对他方法操作安全,防止其他人改变,如对应的变量)
如果在创建实例后,实例的状态再也不发⽣变化,就无需使用Single Threaded Execution模式
4:需要确保安全性时
只有在需要确保安全性时(需要安全性),才需要使用Single Threaded Execution模式
Java的集合类大多数都是⾮线程安全的,这是为了在不需要考虑安全性的时候提高程序运行速度
用户在使用类时,需要考虑自己要用的类是否是线程安全的,来决定是否使用该模式
死锁:
使用Single Threaded Execution模式时,存在发⽣死锁的危险
死锁是指两个线程分别持有锁,并相互等待对方释放锁的现象,发⽣死锁的线程都无法再继续运行,程序卡 死
两个人吃饭,都需要⼑和叉,但⼑叉又只有一套,某时刻,其中一个人拿了⼑,另一个拿了叉,而且两人都在 等待对方让出自己需要的叉或⼑,这种情形下,两个人都只能一直等待下去,这就是发⽣了死锁
在Single Threaded Execution模式中,满⾜下列条件时,会发⽣死锁:
存在多个SharedResource角色(可以是两个类,类里面可以包含多个变量,这里代表锁的共享资源)
线程在持有某个SharedResource角色锁的同时,还想获取其他SharedResource角色的锁
获取SharedResource角色的锁的顺序不固定(SharedResource角色是对称的,我中有你,你中有我)
临界区的大小和性能:
一般情况下,Single Threaded Execution模式会降低程序性能:
1:获取锁花费时间
进入synchronized方法时,线程需要获取对象的锁,该处理会花费时间
如果SharedResource角色的数量减少了,那么要获取的锁的数量也会相应地减少,从而就能够抑制性能的下 降了
2:线程冲突引起的等待
当线程执行临界区内的处理时,其他想要进入临界区的线程会阻塞,这种状况称为线程冲突,发⽣冲突时,程 序的整体性能会随着线程等待时间的增加而下降
总结:简单来说,该模式就相当于一个用一个共享资源(共享的,一般我们也利用他里面的变量,一般即他是共享,也执行他的方法,虽然这里与线程类分开了)来操作锁的模式,所以不是单纯的操作加锁,更简单点:就是对一个可以是多个线程同时进入的地方加上锁的模式
Immutable(英文意思:不变的)模式:
Immutable就是不变的、不发⽣改变,Immutable模式中存在着确保实例状态不发⽣改变的类,在访问这些实 例时不需要执行耗时的互斥处理,如果能用好该模式,就可以提高程序性能
如String就是一个不可变类,immutable的,即对应的值是存在的,只是我们没有指向他了而已,可以看这个博客:https://blog.csdn.net/zhi_sun/article/details/119169738,第20章博客也说明了,一般代表常量,所以不可变(单纯的,不是创建对象,虽然创建对象也算,因为他是一个该模式的类,而单纯的赋值实际上相当于是他进行特殊处理了而已,使得放的位置不同,总体来说源头还是String类)
示例程序:
package main9;

/**
 *
 */
public class User {
    private final Integer userId;
    private final String username;
    private final String desc;

    public User(Integer userId, String username, String desc) {
        this.userId = userId;
        this.username = username;
        this.desc = desc;
    }

    public Integer getUserId() {
        return userId;
    }

    public String getUsername() {
        return username;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", username='" + username + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }

}

package main9;

/**
 *
 */
public class UserThread extends Thread {
    private Integer index = 0;

    private User user;

    public UserThread(User user) {
        this.user = user;
    }

    @Override //该注解只是判断是否是正确重写的,否则他会报错
    public void run() {
        while (true) {

            if (index >= 100) {
                System.exit(0);
            }
            System.out.println(Thread.currentThread().getName() + " ===> " + user);
            index++;

        }
    }
}

package main9;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) throws InterruptedException {
        User user = new User(1001, "张三", "张三是一个好人");
        new UserThread(user).start();
        new UserThread(user).start();
        new UserThread(user).start();
        new UserThread(user).start();
        new UserThread(user).start();

    }
}

很明显,由于操作的是自己对象的变量,并不是共享的,那么对于他来说没有发生改变,因为没有其他人改变他,所以这里对于之前的在Single Threaded Execution模式中,将修改或引用实例状态的地方设置为临界区,该区只能由一个线程执 行,而对于本案例的User类,实例的状态绝对不会发⽣改变,甚至没有操作改变(就算你去操作,由于关键字的原因,也不能改变),所以即使多个线程同时对该实例执行处理,实例也不会出 错,因为实例的状态不变,如此也无需使用synchronized关键字来保护实例,比如下图中的例子,规定不改变

在这里插入图片描述

Immutable模式中的角色:
Immutable:
Immutable角色是一个类,该角色中的字段值不可修改,也不存在修改字段内容的方法,无需对Immutable角 色应用Single Threaded Execution模式,无需使用synchronized关键字,就是本案例的User类

在这里插入图片描述

何时使用Immutable模式:
1:必须是实例创建后,状态不再发⽣变化的,实例的状态由字段的值决定,即使字段是final的且不存在setter, 但是也有可能不是不可变的,因为字段引用的实例有可能发⽣变化,这样与变量的final是无关的,我们只是改变起指向的对象而已,但没有改变该变量,但间接的改变了该变量了
2:实例是共享的,且被频繁访问时
Immutable模式的优点是不需要使用synchronized关键字进行保护,意味着在不失去安全性和⽣存性的前提下 提高性能,当实例被多个线程共享,且有可能被频繁访问时,Immutable模式优点明显
注意:
StringBuffer类表示字符串的可变类(比如可以进行append追加),String类表示字符串的不可变类,String实例表示的字符串不可以修改, 执行操作的方法都不是synchronized修饰的,引用速度更快
如果需要频繁修改字符串内容,则使用StringBuffer,如果不需要修改字符串内容,只是引用内容,则使用String
JDK中的不可变模式类:
java.lang.String,java.math.BigInteger,java.math.Decimal,java.util.regex.Pattern,java.lang.Boolean,java.lang.Byte,java.lang.Character,java.lang.Double,java.lang.Float,java.lang.Integer,java.lang.Long,java.lang.Short,java.lang.Void
可能有其他的类,以及上面可能会随着时间的推移(不同JDk版本)发生改变,但一般是对(不变)的,即主要看JDK版本
总结:就是不操作改变,只给值的模式,所以无论你在多个线程访问时,是否加锁都没有关系,这也验证,线程在得到值时,基本是操作副本的,一般class文件会分开成堆和方法区的(了解即可),我们操作的都是副本(或者说从class文件的副本),所以也验证对应的class只有一个,而我们创建对象时就是拿副本,虽然静态的也是一个,会特殊处理,与class一样,所以class也是可以得到的
Guarded Suspension模式:
Guarded表示被守护、被保卫、被保护,Suspension表示暂停,如果执行现在的处理会造成问题,就让执行 处理的线程进行等待,这就是Guarded Suspension模式
Guarded Suspension模式通过让线程等待来保证实例的安全型,Guarded Suspension也称为guarded wait、spin lock等名称
示例程序:
package main10;

/**
 *
 */
public class Request {
    private final String name;

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

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

package main10;

import java.util.LinkedList;
import java.util.Queue;

/**
 *
 */
public class RequestQueue {
    private final Queue<Request> queue = new LinkedList<>();

    public synchronized Request getRequest() {
        while (queue.peek() == null) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return queue.remove();
    }

    public synchronized void putRequest(Request request) {
        //这里可以进行判断,为null时才进行唤醒,来进行减少唤醒次数,而不是每次都进行唤醒,这里就不修改了
            queue.offer(request);
            notifyAll();

    }
}

package main10;

import java.util.Random;

/**
 *
 */
public class ClientThread extends Thread {
    private final Random random;
    private final RequestQueue requestQueue;

    public ClientThread(RequestQueue requestQueue, String name, long seed) {
        super(name);
        this.requestQueue = requestQueue;
        //给出种子数,默认为系统时间的毫秒数,他只是用来产生随机的结果,与区间没有关系,具体可以看这个博客:https://blog.csdn.net/weixin_35182280/article/details/114873301
        //所以这个是否加上并没有什么问题
        this.random = new Random(seed);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Request request = new Request("请求:" + i);
            System.out.println(Thread.currentThread().getName() + " 请求 " + request);
            requestQueue.putRequest(request);
            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package main10;

import java.util.Random;

/**
 *
 */
public class ServerThread extends Thread {
    private final Random random;
    private final RequestQueue requestQueue;

    public ServerThread(RequestQueue requestQueue, String name, long seed) {
        super(name);
        this.requestQueue = requestQueue;
        random = new Random(seed);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Request request = requestQueue.getRequest();
            System.out.println(Thread.currentThread().getName() + " 处理 " + request);
            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package main10;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) {
        RequestQueue requestQueue = new RequestQueue();
        new ClientThread(requestQueue, "client-1", 432432L).start();
        new ServerThread(requestQueue, "server-1", 9988766L).start();
    }
}

应用保护条件进行保护:
public synchronized Request getRequest() { //这个synchronized
        while (queue.peek() == null) { //这个判断
            try {
                wait(); //需要锁的,上面的synchronized基本要加,而sleep就是可以不用锁的,一般来说,为了防止线程在没有锁的地方进行阻塞,规定sleep没有永久的阻塞(虽然可以设置很大的时间),这是因为没有线程是永久执行的,而不像wait一样的放弃cpu,cpu一般代表操作权限,如果前面有说明不是这样的,那么以这个为准
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return queue.remove(); //这个删除(出队,因为是队列,从头出,而添加是从尾部,自然要认为是先进先出的哦)
    }
上面代码中,getRequest方法执行的逻辑是从queue中取出一个Request实例,即 queue.remove() ,但是要获取Request实例,必须满⾜条件: queue.peek() != null ,该条件就是Guarded Suspension模式的守护条件 (guard condition:英文意思:防护条件,所以这里称为守护条件),防止null的时候进行删除,使得出现异常
当线程执行到while语句时:
若守护条件成立,线程不进入while语句块,直接执行queue.remove()方法,线程不会等待
若守护条件不成立,线程进入while语句块,执行wait,开始等待
public synchronized void putRequest(Request request) {
            queue.offer(request);
            notifyAll(); //是wait的配套的(虽然是两个中的其中一个),自然也要锁
    //这里并不需要全部唤醒,因为只有一个,所以notify也行

    }
若守护条件不成立,则线程等待,等待什么?等待notifyAll()唤醒该线程
守护条件阻⽌了线程继续向前执行,除⾮实例状态发⽣改变,守护条件成立,被另一个线程唤醒
该类中的synchronized关键字保护的是queue字段,getRequest方法的synchronized保护该方法只能由一个 线程执行(虽然也是阻塞唤醒的条件,因为阻塞唤醒就是针对操作一人的,而不是sleep一样的,即各有所长)
线程执行this.wait之后,进入this的等待队列,并释放持有的this锁
notify、notifyAll或interrupt会让线程退出等待队列,实际继续执行之前还必须再次获取this的锁线程才可以 继续执行
相关的细节说明在前面已经说过了,这里回顾一下而已
时序图(有时候给出,是因为比较复杂的时候给出,或者是基础的给出,其中如果是相对应的后面有类似的可能只给出类图(是可能,并不是一定,所以要注意)):

在这里插入图片描述

这个图从上到下的看就可以了(ok代表成功操作,即成功放入或者成功得到)
Guarded Suspension模式中的角色:
GuardedObject(被保护的对象):
GuardedObject角色是一个持有被保护(guardedMethod)的方法的类,当线程执行guardedMethod方法 时,若守护条件成立,立即执行,当守护条件不成立,则等待,守护条件是可以随着GuardedObject角色的状态不同而变的(因为是使用对应的变量的)
除了guardedMethod之外,GuardedObject角色也可以持有其他改变实例状态(stateChangingMethod,带这个括号的,一般代表该别名,即这里是实例状态)的 方法,那么实际上Single Threaded Execution模式也可以,只需要共享资源的类来实现改变其他状态即可,但是对应的线程只是基本操作他而已,所以并没有意义
java中,guardedMethod通过while语句和wait方法来实现,stateChangingMethod通过notify/notifyAll方法 实现(不阻塞,使得进行出队)
在本案例中,RequestQueue为GuardedObject,getRequest方法为guardedMethod(也可以是stateChangingMethod,因为是"除了guardedMethod之外",上面的话术说明),putRequest为stateChangingMethod
可以将Guarded Suspension理解为多线程版本的if,满足条件进入,否则不进入
我们也可以使用LinkedBlockingQueue替代对应的队列(自带的阻塞队列,那么我们就不用自己实现阻塞和唤醒了,前面说明过他了)
package main10;

import java.util.concurrent.LinkedBlockingQueue;

/**
 *
 */
public class RequestQueue {
    private final LinkedBlockingQueue<Request> queue = new LinkedBlockingQueue<>();

    public Request getRequest() {
        Request request = null;
        try {
            request = queue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return request;
    }

    public void putRequest(Request request) {
        try {
            queue.put(request);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

类图:

在这里插入图片描述

虽然之前用的是不安全的,但是由于我们操作了锁,且只有一人(一个线程,对应的一个消费者或者生产者,是或者,他们并没有一起执行,因为是同一个this,即同一个引用,若他们一起可能就有问题了,因为是线程不安全的,那么在指向上可能会覆盖,使得没有删除或者没有添加对应的同一个位置,这是多线程的一个大众问题),所以基本不会出现问题
总结:就是在多线程中进行判断,使得不会出现问题或者避免出现问题的模式
Balking模式:
所谓Balk,就是停⽌并返回的意思
Balking模式与Guarded Suspension模式一样,也存在守护条件,在Balking模式中,如果守护条件不成立, 则立即中断处理,而Guarded Suspension模式一直等待直到可以运行
示例程序:
这里简单说明一下流程(因为这里可能会复杂点):
两个线程,一个是修改线程,修改之后,等待随机时长,保存文件内容
另一个是保存线程,固定时长进行文件内容的保存(一开始不等,之后就按照固定时长了,所以打印时第一个基本都会出现"2不需要保存"的内容),如果文件需要保存,则执行保存动作,如果文件不需要保存,则不执行保存动作
package main11;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 *
 */
public class Data {
    private final String filename; //文件地址
    private String content; //文件内容
    private boolean changed;

    public Data(String filename, String content) {
        this.filename = filename;
        this.content = content;
    }

    public synchronized void change(String newContent) {
        this.content = newContent;
        this.changed = true;
    }

    public synchronized void save() throws IOException {
        if (changed) {
            doSave();
            changed = false;
        } else {
            System.out.println(Thread.currentThread().getName() + "不需要保存");
        }
    }

    private void doSave() throws IOException {
        System.out.println(Thread.currentThread().getName() + " 调用doSave,内容为:" + content);
        Writer writer = new FileWriter(filename); //一般写操作会先清空文件内容哦
        writer.write(content);
        writer.close();
    }
}

package main11;

import java.io.IOException;

/**
 *
 */
public class SaverThread extends Thread {
    private final Data data;

    public SaverThread(String name, Data data) {
        super(name);
        this.data = data;
    }

    @Override
    public void run() {
        while (true) {
            try {
                data.save();
                Thread.sleep(1000);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package main11;

import java.io.IOException;
import java.util.Random;

/**
 *
 */
public class ChangerThread extends Thread {
    private final Data data;
    private final Random random = new Random();

    public ChangerThread(String name, Data data) {
        super(name);
        this.data = data;
    }

    @Override
    public void run() {
        for (int i = 0; true; i++) {
            data.change("第 " + i + " 次修改");
            try {
                Thread.sleep(random.nextInt(2000));
                data.save();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package main11;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) {
        Data data = new Data("F:/run.txt","嘿嘿"); //虽然这个嘿嘿并没有意义,因为一般直接改变了,而没有在控制台上打印出来
        new ChangerThread("1",data).start();
        new SaverThread("2",data).start();
    }
}

很明显,中断处理就是没有执行对应的方法,而不是等待执行
Balking模式中的角色:
GuardedObject(受保护对象):
GuardedObject角色是一个拥有被保护的方法(guardedMethod)的类,当线程执行guardedMethod时,若 保护条件成立,则执行实际的处理,若不成立,则不执行实际的处理,直接返回
保护条件的成立与否随着GuardedObject角色状态的改变而变动
除了guardedMethod之外,GuardedObject角色还有可能有其他改变状态的方法 (stateChangingMethod)
在此案例中,Data类对应于GuardedObject,save方法对应guardedMethod(也可以是stateChangingMethod),change方法对应stateChangingMethod方法
保护条件是changed字段为true(这里就提一下了)
类图:

在这里插入图片描述

何时使用Balking模式(前面的Guarded Suspension模式好像没有这样的说明,即并不是非常重要的,或者是有这个类似的,才没有说明):
1:不需要执行时
在此示例程序中,content字段的内容如果没有修改,就将save方法balk,之所以要balk,是因为content已经 写文件了,无需再写了(这个意思代表,如果我没有修改的话,说明已经写入一次了,虽然第一次也出现了,但是这也只是第一次而已,之后基本就是这样的),所以,在这种情况下,如果并不需要执行某些代码,就可以使用Balking模式,此时可以提高程序性能,而不是等待执行(阻塞唤醒是需要时间的,虽然这里是不使用的,如果要使用需要考虑很多问题,只需要写入后进行设置即可)
2:不需要等待守护条件成立时
Balking模式的特点就是不等待,若条件成立,就执行,若不成立,就不执行,立即进入下一个操作
3:守护条件仅在第一次成立时
当"守护条件仅在第一次成立或者需要第一次操作"时,可以使用Balking模式
比如各种类的初始化操作,检查一次是否初始化了,如果初始化了,就不用执行了,如果没有初始化,则进行 初始化
balk结果的表示:
1:忽略balk
最简单的方式就是不通知调用端"发⽣了balk",示例程序采用的就是这种方式,即没有操作返回值
2:通过返回值表示balk
可以通过boolean值表示balk,若返回true,表示未发⽣balk,需要执行并执行了处理,若false,则表示发⽣了balk,处理已执行,不再需要执行,有时也会使用null来表示"发⽣了balk",不只是false一种哦,具体可以自己设置,比如0,1也行,这要看自己
3:通过异常表示balk
有时也通过异常表示"发⽣了balk",即,当balk时,程序并不从方法return,而是抛异常
Balking和Guarded Suspension模式之间:
介于"直接balk并返回"和"等待到守护条件成立为⽌"这两种极端之间的还有一种"在守护条件成立之前等待一段 时间",在守护条件成立之前等待一段时间,如果到时条件还未成立,则直接balk
这种操作称为计时守护(guarded timed)或超时(timeout)
java.util.concurrent中的超时:
1:通过异常通知超时
一般当发⽣超时抛出异常时,且不适合使用返回值表示超时,通常需要使用java.util.concurrent.TimeoutException异 常,当然也可以自己操作异常,但是一般是这个(现在JDK自带的一般操作他,因为总要有个共享的异常吧,为什么不是他呢)
如:
java.util.concurrent.Future的get方法
java.util.concurrent.Exchanger的exchange方法
java.util.concurrent.Cyclicarrier的await方法
java.util.concurrent.CountDownLatch的await方法
具体可能不会这样,但是现在一般是正确的,可能还有其他,主要看JDK版本吧
2:通过返回值通知超时
当执行多次try时,则不使用异常,而使用返回值表示超时
如:
java.util.concurrent.BlockingQueue接口,当offer方法的返回值为false,或poll方法的返回值为null,表示发 ⽣了超时
java.util.concurrent.Semaphore类,当tryAcquire方法的返回值为false时,表示发⽣了超时
java.util.concurrent.locks.Lock接口,当tryLock方法的返回值为false时,表示发⽣了超时
也是看JDK版本
总结:该模式就是Guarded Suspension模式不进行等待的模式,虽然还有他和Guarded Suspension模式之间的特殊操作,但也属于Guarded Suspension模式的说明
Producer-Consumer模式:
⽣产者安全地将数据交给消费者,当⽣产者和消费者以不同的线程运行时,两者之间的处理速度差异会有问题,⽣产者消费者模式用于消除线程间处理速度的差异带来的问题,在该模式中,⽣产者和消费者都有多个,当⽣产者和消费者只有一个时,我们称为管道(Pipe)模式,就类似与前面的Guarded Suspension模式(或者就是他),各自只有一个(但是他们确只能一人进入,但是就算是多个,也是一样的只能一人进入,除非是操作边界通知,"边界通知"在前面说明过了)
示例程序:
package main12;

/**
 *
 */
public class Table {
    private final String[] buffer;
    private int tail;
    private int head;
    private int count;

    public Table(int count) {
        this.buffer = new String[count];
        this.head = 0;
        this.tail = 0;
        this.count = 0;
    }

    public synchronized void put(String steamedBread) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " 蒸出来 " + steamedBread);
        while (count >= buffer.length) {
            wait();
        }
        buffer[tail] = steamedBread;
        tail = (tail + 1) % buffer.length;
        count++;
        notifyAll();
    }

    public synchronized String take() throws InterruptedException {
        while (count <= 0) {
            wait();
        }
        String steamedBreak = buffer[head];
        head = (head + 1) % buffer.length;
        count--;
        notifyAll();
        System.out.println(Thread.currentThread().getName() + " 取⾛ " + steamedBreak);
        return steamedBreak;
    }
}

package main12;

import java.util.Random;

/**
 *
 */
public class CookerThread extends Thread {
    private final Random random;
    private final Table table;
    private static int id = 0;

    public CookerThread(String name, Table table, long seed) {
        super(name);
        this.table = table;
        this.random = new Random(seed);
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(1000));
                String steamedBread = "[ Steamed bread No. " + nextId() + " by " + getName() + "]";
                table.put(steamedBread);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    private static synchronized int nextId() {
        return id++;
    }
}

package main12;

import java.util.Random;

/**
 *
 */
public class EaterThread extends Thread {
    private final Random random;
    private final Table table;

    public EaterThread(String name, Table table, long seed) {
        super(name);
        this.table = table;
        this.random = new Random(seed);
    }

    @Override
    public void run() {
        try {
            while (true) {
                String steamedBread = table.take();
                Thread.sleep(random.nextInt(1000));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package main12;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) {
        Table table = new Table(3);
        new CookerThread("厨师-1", table, 12345L).start();
        new CookerThread("厨师-2", table, 23456L).start();
        new CookerThread("厨师-3", table, 34567L).start();

        new EaterThread("饭桶-1", table, 45678L).start();
        new EaterThread("饭桶-2", table, 56789L).start();
        new EaterThread("饭桶-3", table, 67890L).start();
    }
}

之前有个类似的即多个消费者的地方的代码(前面有过说明多个消费者或者多个生产者的地方,比如:边界通知,比如"我们给出了疑问:“这里在后面会进行说明”",当然这些地方都进行了说明,一般还有其他地方,这里就不多说了),简单来说就是操作多个生成(产)者和多个消费者的模式
关于put方法:
put方法会抛出InterruptedException异常(中断的异常),如果抛出,可以理解为"该操作已取消"
put方法使用了Guarded Suspension模式
tail和count的更新采取buffer环的形式(1,2,3,1,2,3这样的顺序,前面也有过类似的案例,比如MyQueue类)
notifyAll方法唤醒正在等待馒头(这里将生产的东西简称为馒头了)的线程来吃
关于take方法:
take方法会抛出InterruptedException异常,表示该操作已取消
take方法采用了Guarded Suspension模式
head和count的更新采用了buffer环的形式
notifyAll唤醒等待的厨子线程开始蒸馒头
时序图:

在这里插入图片描述

Producer-Consumer模式中的角色:
1:Data(英文意思:数据)
Data角色由Producer角色⽣成,供Consumer角色使用,在本案例中,String类的馒头对应于Data角色
2:Producer
Producer角色⽣成Data角色,并将其传递给Channel角色,本案例中,CookerThread对应于Producer角色
3:Consumer
Consumer角色从Channel角色获取Data角色并使用,本案例中,EaterThread对应于Consumer角色
4:Channel角色
Channel角色管理从Producer角色获取的Data角色,还负责响应Consumer角色的请求,传递Data角色,为 了安全,Channel角色会对Producer角色和Consumer角色进行互斥处理
当producer角色将Data角色传递给Channel角色时,如果Channel角色状态不能接收Data角色,则Producer角色将一直等待,直到Channel可以接收Data角色为⽌,当Consumer角色从Channel角色获取Data角色时,如果Channel角色状态没有可以传递的Data角色,则Consumer角色将一直等待,直到Channel角色状态转变为可以传递Data角色为⽌,当存在多个Producer角色和Consumer角色时,Channel角色需要对它们做互斥处理,很明显对应的Table就是该角色(包含Date角色)
类图:

在这里插入图片描述

守护安全性的Channel角色(可复用):
在⽣产者消费者模型中,承担安全守护责任的是Channel角色,Channel角色执行线程间的互斥处理,确保Producer角色正确地将Data角色传递给Consumer角色
不要直接传递:
Consumer角色想要获取Data角色,通常是因为想使用这些Data角色来执行某些处理,如果Producer角色直 接调用Consumer的方法,执行处理的就不是Consumer的线程,而是Producer角色的线程了,这样一来,异步处 理变同步处理(虽然也是,因为加锁,但是若是边界通知那么是这样,虽然这里不是,即这里需要考虑边界通知),自然会发⽣不同Data间的延迟,降低程序的性能
传递Data角色的顺序可以有如下:
1:队列——先⽣产先消费(上面的代码本质上应该是这个,因为是一路过去的,自然是先生产先消费)
2:栈——先⽣产后消费
3:优先队列——"优先"的先消费(某些比较)
Channel意义:
线程的协调要考虑"放在中间的东⻄"(对应的位置移动以及条件)
线程的互斥要考虑"应该保护的东⻄"(对应的锁)
为了让线程协调运行,必须执行互斥处理,以防⽌共享的内容被破坏,线程的互斥处理时为了线程的协调运行 而执行的
JUC包和Producer-Consumer模式:
JUC中提供了BlockingQueue接口及其实现类,相当于Producer-Consumer模式中的Channel角色

在这里插入图片描述

BlockingQueue接口——阻塞队列
ArrayBlockingQueue——基于数组的BlockingQueue
LinkedBlockingQueue——基于链表的BlockingQueue
PriorityBlockingQueue——带有优先级的BlockingQueue
DelayQueue——一定时间之后才可以take的BlockingQueue
SynchronousQueue——直接传递的BlockingQueue
ConcurrentLinkedQueue——元素个数没有最大限制的线程安全队列
上面的队列在前面也大致说明过了
总结:就是完成操作多个生产者和多个消费者的模式
Read-Write Lock模式:
当线程读取实例的状态时,实例的状态不会发⽣变化,实例的状态仅在线程执行写入操作时才会发⽣变化
从实例状态变化来看,读取和写入有本质的区别
在本模式中,读取操作和写入操作分开考虑,在执行读取操作之前,线程必须获取用于读取的锁,在执行写入 操作之前,线程必须获取用于写入的锁
可以多个线程同时读取,读取时不可写入,当线程正在写入时,其他线程不可以读取或写入(注意:包含读取哦,因为写入是改变的,是特殊的,相当于读写锁)
一般来说,执行互斥会降低程序性能,如果把写入的互斥和读取的互斥分开考虑,则可以提高性能,相当于不用考虑对方或者大概率使用没有互斥的一方,自然相对应的对方代码就少(之所以有(就少,说明有联系),是因为可能需要判断是否是写还是读)或者没有,以及可能互斥的少(因为很多时候可能是使用没有互斥的地方,如读读),自然会提高效率
现在我们来完成这个模式,或者说,手写一个读写锁
示例程序:
package main13;

/**
 *
 */
public class Data {
    private final char[] buffer;
    // Data在线程之间共享
    // Data保有读写锁
    private final ReadWriteLock lock = new ReadWriteLock();

    public Data(int size) {
        this.buffer = new char[size];
        for (int i = 0; i < buffer.length; i++) {
            buffer[i] = '*';
        }
    }

    public char[] read() throws InterruptedException {
        lock.readLock();
        try {
            return doRead();
        } finally {
            lock.readUnlock();
        }
    }

    public void write(char c) throws InterruptedException {
        // 加锁,当前线程执行写操作
        lock.writeLock();
        try {
            // 写操作
            doWrite(c);
        } finally {
            // 写入完成,释放锁,同时唤醒其他等待的线程
            lock.writeUnlock();
        }
    }

    private char[] doRead() {
        char[] newbuf = new char[buffer.length];
        for (int i = 0; i < buffer.length; i++) {
            newbuf[i] = buffer[i];
        }
        slowly();
        return newbuf;
    }

    private void doWrite(char c) {
        for (int i = 0; i < buffer.length; i++) {
            buffer[i] = c;
            slowly();
        }
    }

    private void slowly() {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package main13;

/**
 *
 */
public class ReaderThread extends Thread {
    private final Data data;

    public ReaderThread(Data data) {
        this.data = data;
    }

    @Override
    public void run() {
        try {
            while (true) {
                char[] readbuf = data.read();
                System.out.println(Thread.currentThread().getName() + " 读取了 " + String.valueOf(readbuf));
            }
        } catch (InterruptedException e) {
        }
    }
}

package main13;

import java.util.Random;

/**
 *
 */
public class WriterThread extends Thread {
    private static final Random RANDOM = new Random();
    private final Data data;
    private final String filler;
    private int index = 0;

    public WriterThread(Data data, String filler) {
        this.data = data;
        this.filler = filler;
    }

    @Override
    public void run() {
        try {
            while (true) {
                // 使用轮询的方法,读取当前一个字节
                char c = nextchar();
                // 写字节
                data.write(c);
                Thread.sleep(RANDOM.nextInt(3000));
            }
        } catch (InterruptedException e) {
        }
    }

    private char nextchar() {
        char c = filler.charAt(index);
        index++;
        if (index >= filler.length()) {
            index = 0;
        }
        return c;
    }
}


package main13;

/**
 *
 */
public class ReadWriteLock {
    private int readingReaders = 0;
    private int waitingWriters = 0;
    private int writingWriters = 0;
    private boolean preferWriter = true;

    /**
     * 对于读操作:
     * 1. 如果有正在写入的writer,则当前线程等待writer写入完成
     * 2. 如果没有正在写入的writer,但是有正在等待写入的writer,同时偏向写操作(最终作用相当于是:等待所有对应的写操作(开始写也算,只要进入了对应的方法就算,这是因为对应的第一步就是waitingWriters++;)完毕)
     * 则当前线程等待writer写入完成
     *
     * @throws InterruptedException
     */

    public synchronized void readLock() throws InterruptedException {
        while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) {
            // 当前线程等待
            wait();
        }
        readingReaders++;
    }

    public synchronized void readUnlock() {
        readingReaders--;
        preferWriter = true;
        // 唤醒在当前对象上等待的其他线程
        notifyAll();
    }

    public synchronized void writeLock() throws InterruptedException {
        waitingWriters++;
        try {
            // 如果有正在读取的线程,或者有正在写入的线程,则当前线程等待被唤醒
            while (readingReaders > 0 || writingWriters > 0) {
                wait();
            }
        } finally {
            waitingWriters--;
        }
        writingWriters++;
    }

    public synchronized void writeUnlock() {
        writingWriters--;
        preferWriter = false;
        // 当前线程写入完成,唤醒其他等待的线程进行读写操作
        notifyAll();
    }
}

package main13;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) {
        Data data = new Data(10);
        // 启动读取线程,读取同一个对象中的数据
        new ReaderThread(data).start();
        new ReaderThread(data).start();
        new ReaderThread(data).start();
        new ReaderThread(data).start();
        new ReaderThread(data).start();
        new ReaderThread(data).start();
        // 启动写线程,两个线程一个写大写,另一个写小写
        new WriterThread(data, "ABCDEFGHIJKLMNOPQRSTUVWXYZ").start();
        new WriterThread(data, "abcdefghijklmnopqrstuvwxyz").start();
    }
}

大致流程就是读操作时,写不能操作,但是读可以,而写操作读和写都不可以,最主要的就是阻塞和唤醒之间的细节了,其中保证了阻塞中,对应下一个必然会到唤醒,使得不会都进行阻塞,具体需要自己理解
守护条件:
readLock方法和writeLock方法都是用了Guarded Suspension模式,Guarded Suspension模式的重点是守护 条件
readLock方法:
读取线程⾸先调用readLock方法,当线程从该方法返回,就可以执行实际的读取操作,当线程开始执行实际的读取操作时,只需要判断是否存在正在写入的线程,以及是否存在正在等待的写入线 程, 不考虑读取线程,如果存在正在写入的线程或者存在正在等待的写线程,则等待
writeLock方法:
在线程开始写入之前,调用writeLock方法,当线程从该方法返回后,就可以执行实际的写入操作,开始执行写入的条件:如果有线程正在执行读取操作,出现读写冲突,或者如果有线程正在执行写入的操作, 引起写冲突,当前线程等待
Read-Write Lock模式中的角色:
Reader:
该角色对共享资源角色执行读取操作,在案例中就是ReaderThread类
Writer:
该角色对共享资源角色执行写操作,在案例中就是WriterThread类
SharedResource:
共享资源角色表示Reader角色和Writer角色共享的资源,共享资源角色提供不修改内部状态的操作(读取)和 修改内部状态的操作(写),当前案例中对应于Data类
ReadWriteLock:
写锁角色提供了共享资源角色实现读操作和写操作时需要的锁,即当前案例中的readLock和readUnlock, 以及writeLock和writeUnlock,对应于当前案例中ReadWriteLock类

在这里插入图片描述

只是一个图,这里就不给出说明(类图)了
要点:
1:利用读取操作的线程之间不会冲突的特性来提高程序性能
Read-Write Lock模式利用了读操作的线程之间不会冲突的特性,由于读取操作不会修改共享资源的状 态,所以彼此之间无需加锁,因此,多个Reader角色同时执行读取操作,从而提高程序性能
2:适合读取操作负载较大的情况
如果单纯使用Single Threaded Execution模式,则read也只能运行一个线程,如果read负载很重,可以 使用Read-Write Lock模式
3:适合少写多读
Read-Write Lock模式优点是Reader之间不会冲突,如果写入很频繁,Writer会频繁停⽌Reader的处 理,也就无法体现出Read-Write Lock模式的优势了,所以这个时候也就与互斥差不多了
锁的含义:
synchronized可以用于获取实例的锁,java中同一个对象锁不能由两个以上的线程同时获取
用于读取的锁和用于写入的锁与使用synchronized获取的锁是不一样的,开发人员可以通过修改ReadWriteLock类来改变锁的运行(就如他是我们写的一样,自然可以改变)
ReadWriteLock类提供了用于读取的锁和用于写入的锁两个逻辑锁,但是实现这两个逻辑锁的物理锁只有一 个,就是ReadWriteLock实例持有的锁(也就是this,操作synchronized,因为他们之间要互通,自然需要同一个锁,即操作wait和notify(包括notifyAll)的操作)
JUC包和Read-Write Lock模式:
java.util.concurrent.locks包提供了已实现Read-Write Lock模式的ReadWriteLock接口和ReentrantReadWriteLock类
java.util.concurrent.locks.ReadWriteLock接口的功能和当前案例中的ReadWriteLock类类似,不同之处在于 该接口用于读取的锁和用于写入的锁是通过其他对象来实现的

在这里插入图片描述

java.util.concurrent.locks.ReentrantReadWriteLock类实现了ReadWriteLock接口,其特征如下:
1:公平性
当创建ReentrantReadWriteLock类(前面好像也大致说明过)的实例时,可以选择锁的获取顺序是否要设置为fair的,如果创建的 实例是公平的,那么等待时间久的线程将可以优先获取锁(够久了终于到我了吧)
2:可重入性
ReentrantReadWriteLock类的锁是可重入的,Reader角色的线程可以获取用于写入的锁,Writer角色的 线程可以获取用于读取的锁(只是重入,并不是一定操作具体作用,但是可能会保留某些东西,使得可以锁降级,这里在前面可能没有说明,这里补充一下)
3:锁降级
ReentrantReadWriteLock类可以按如下顺序将用于写入的锁降级为用于读取的锁:

在这里插入图片描述

用于读取的锁不能升级为用于写入的锁,即对应的写重入时,可能不会保留某些东西
4:快捷方法
ReentrantReadWriteLock类提供了获取等待中的线程个数的方法 getQueueLength ,以及检查是否获取 了用于写入锁的方法 isWriteLocked 等方法,他总该有其他的方法吧,而不是我们的这里的关键方法,就如在99章博客(如果有上篇和下篇和中篇,一般这样的说明代表是上下篇,可能也包括中篇,只是给出起点篇而已,所以可能存在100章博客中,所以若有上篇和下篇或者包括中篇,那么99就代表99和100,或者也有101,虽然99并没有,而中篇的在这里的博客,即101,102,103)中一个手写的栈就可以操作反向打印(查询)(自己写的其他方法哦,一般代表除了关键方法外,如添加,删除等等,我们一般将他们这样的方法称为补充方法,也称快捷方法,即其他(作用)的方法)
总结:相当于读写锁的逻辑,这里没有什么总结的,只要看懂代码流程即可
Thread-Per-Message模式:
该模式可以理解为"每个消息一个线程",消息这里可以理解为命令或请求,每个命令或请求分配一个线程,由 这个线程来处理,这就是Thread-Per-Message模式,在Thread-Per-Message模式中,所以自然消息的委托方和执行方是不同的线程,因为准备执行时,由其他人执行,类似于前面的ForkJoinPool类的分开操作,一个线程分开多个,只是他并没有什么等待操作,且没有线程保存
示例程序:
在此示例程序中,ConcurrentDemo类委托Host来显示字符,Host类会创建一个线程,来处理委托,启动的 线程使用Helper类来执行实际的显示
package main14;

/**
 *
 */
public class Host {
    private final Helper helper = new Helper();

    public void request(final int count, final char c) {
        System.out.println("\t请求:【" + count + "," + c + "】开始。。。");
        new Thread() {
            @Override
            public void run() {
                helper.handle(count, c);
            }
        }.start();
        System.out.println("\t请求:【" + count + "," + c + "】结束!!!");
    }
}

package main14;

/**
 *
 */
public class Helper {
    public void handle(int count, char c) {
        System.out.println("\t\t处理:【" + count + "," + c + "】开始。。。");
        for (int i = 0; i < count; i++) {
            slowly();
            System.out.print(c);
        }
        System.out.println("");
        System.out.println("\t\t处理:【" + count + "," + c + "】结束!!!");

    }

    private void slowly() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package main14;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) {
        System.out.println("主线程 -- 开始执行");
        Host host = new Host();
        host.request(10, 'A');
        host.request(20, 'B');
        host.request(30, 'C');
        System.out.println("主线程 -- 执行结束");
    }
}

在这里插入图片描述

Thread-Per-Message模式中的角色:
Client(委托方):
Client角色向Host角色发起请求,而不用关⼼Host角色如何实现该请求处理,当前案例中对应于ConcurrentDemo类,更简单(具体点)的就是main对应的线程,即主线程
Host:
Host角色收到Client角色请求后,创建并启用一个线程,新建的线程使用Helper角色来处理请求,当前案例中对应于Host类
Helper:
Helper角色为Host角色提供请求处理的功能,Host角色创建的新线程调用Helper角色,当前案例中对应于Helper类

在这里插入图片描述

要点:
1:提高响应性,缩短延迟时间
Thread-Per-Message模式能够提高与Client角色对应的Host角色的响应性,降低延迟时间,尤其是当handle操作⾮常耗时或者handle操作需要等待输入/输出时,效果很明显,即进行了分开操作,其中我们也可以为了缩短线程启动花费的时间,可以使用Worker Thread模式(后面会说明)
2:适用于操作顺序没有要求时
在Thread-Per-Message模式中,handle方法并不一定按照request方法的调用顺序来执行(就如多线程是抢占cpu来决定谁先执行的,所以多次的执行顺序不一样,所以需要操作没有顺序要求的,否则可能需要某些延迟操作,比较麻烦一点)
3:适用于不需要返回值时
在Thread-Per-Message模式中,request方法并不会等待handle方法的执行结束,即request是通常得不到handle的结果(因为是新的线程),当需要获取操作结果时,可以使用Future模式(后面会说明)
4:应用于服务器(具体如何应用,可以选择百度)
JUC包和Thread-Per-Message模式:
java.lang.Thread类:最基本的创建、启动线程的类
java.lang.Runnable接口:线程锁执行的任务接口
java.util.concurrent.ThreadFactory接口:将线程创建抽象化的接口
java.util.concurrent.Executors:用于创建实例的工具类
java.util.concurrent.Executor接口:将线程执行抽象化的接口
java.util.concurrent.ExecutorService接口:将被复用的线程抽象化的接口
java.util.concurrent.ScheduledExecutorService类:将被调度线程的执行抽象化的接口

在这里插入图片描述

总结:看成一个线程里面在创建线程即可,或者就是一个低配的线程池(只创建线程,线程池在当前主线程里操作,自然是一个线程里面创建线程)
Worker Thread模式:
在Worker Thread模式中,工人线程(worker thread)会逐个取回工作并进行处理,当所有工作全部完成 后,工人线程会等待新的工作到来,简单的说就是线程池操作,所以可以在前面这样说"其中我们也可以为了缩短线程启动花费的时间,可以使用Worker Thread模式(后面会说明)",即他是重复利用的(等待继续执行,执行后,继续等待(可以认为是自旋或者阻塞中))
Worker Thread模式也被称为Background Thread模式,有时也称为Thread Pool模式
示例程序:
ClientThread类的线程会向Channel类发送工作请求(委托)
Channel类的实例有五个工人线程进行工作,所有工人线程都在等待工作请求的到来
当收到工作请求后,工人线程会从Channel获取一项工作请求并开始工作,工作完成后,工人线程回到Channel那里等待下一项工作请求
package main15;

/**
 *
 */
public class Channel {
    private static final int MAX_REQUEST = 100;
    private final Request[] requestQueue;
    private int tail;
    private int head;
    private int count;

    private final WorkerThread[] threadPool;

    public Channel(int threads) {
        this.requestQueue = new Request[MAX_REQUEST];
        this.head = 0;
        this.tail = 0;
        this.count = 0;

        threadPool = new WorkerThread[threads];
        for (int i = 0; i < threadPool.length; i++) {
            threadPool[i] = new WorkerThread("Worker-" + i, this); //最初的this,也就是该对象(或者说最终的引用)
        }
    }

    public void startWorkers() {
        for (int i = 0; i < threadPool.length; i++) {
            threadPool[i].start();
        }
    }

    public synchronized void putRequest(Request request) {
        while (count >= requestQueue.length) {
            try {
                wait();
            } catch (InterruptedException e) {

            }
        }

        requestQueue[tail] = request;
        tail = (tail + 1) % requestQueue.length;
        count++;
        notifyAll();
    }

    public synchronized Request takeRequest() {
        while (count <= 0) {
            try {
                wait();
            } catch (InterruptedException e) {

            }
        }

        Request request = requestQueue[head];
        head = (head + 1) % requestQueue.length;
        count--;
        notifyAll();
        return request;
    }
}

package main15;

import java.util.Random;

/**
 *
 */
public class ClientThread extends Thread{
    private final Channel channel;
    private static final Random RANDOM = new Random();

    public ClientThread(String name, Channel channel) {
        super(name);
        this.channel = channel;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; true; i++) {
                Request request = new Request(getName(), i);
                channel.putRequest(request);
                Thread.sleep(RANDOM.nextInt(1000));
            }
        } catch (InterruptedException e) {

        }
    }
}

package main15;

/**
 *
 */
public class ConcurrentDemo {
    public static void main(String[] args) {
        Channel channel = new Channel(5);
        channel.startWorkers();
        new ClientThread("张三", channel).start();
        new ClientThread("李四", channel).start();
        new ClientThread("王五", channel).start();
    }
}

package main15;

import java.util.Random;

/**
 *
 */
public class Request {
    private final String name;
    private final int number;
    private static final Random RANDOM = new Random();

    public Request(String name, int number) {
        this.name = name;
        this.number = number;
    }

    public void execute() {
        System.out.println(Thread.currentThread().getName() + " 执行 " + this);
        try {
            Thread.sleep(RANDOM.nextInt(1000));
        } catch (InterruptedException e) {

        }
    }

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

package main15;

/**
 *
 */
public class WorkerThread extends Thread{
    private final Channel channel;

    public WorkerThread(String name, Channel channel) {
        super(name);
        this.channel = channel;
    }

    @Override
    public void run() {
        while (true) {
            Request reqeust = channel.takeRequest();
            reqeust.execute();
        }
    }
}

在这里插入图片描述

在这里插入图片描述

相当于多个线程在阻塞,只要你有一个,那么他们继续进行操作,然后继续阻塞,虽然这里将对应的拿取操作称为任务,使得相当于之前的Producer-Consumer模式了,但是主要的区别是,其中的生产是我们来进行操作的(虽然这里是操作无限循环,也就是无限的给任务,这就是主要区别),抛出与他的区别,也验证了他的线程是一直存在的,也就不用再次的创建线程来执行了(因为无限循环,自然使得他还是起作用,而不是销毁,使得不用创建线程了,即重复利用,即相当于线程池),实际上也验证了线程池的原理也算是一个典型的⽣产者—消费者模型,只是将对应的操作的同一个数据(如数组)改成任务(也可以是数组)了,所以一般的,我们将该模式也可以称为Producer-Consumer模式,只不过他倾向于一个固定的数量(如线程数量并不会很多),即将对方(用户)的操作者,变成自己(自己的线程,而不是用户的线程)的转变了,即是一个特殊的Producer-Consumer模式,用户(我们的人创建的线程,或者就是人,只是人消费时会是线程,但一般也是操作线程池的)变成内部(内部自己保存的线程)了,所以实际上Producer-Consumer模式与Worker Thread模式原理基本一样,本质只是量级的大小,Worker Thread模式比较小点(针对消费来说的,因为是完成,所以是针对消费),而Producer-Consumer模式一般根据用户来说的(如服务器,由于是Thread-Per-Message模式的补充(所以他Worker Thread模式也可以是该模式的补充),所以本质上才会说Thread-Per-Message模式的要点中有:“应用于服务器(具体如何应用,可以选择百度)”)
Worker Thread模式中的角色:
Client(委托者):
Client角色创建Request角色并将其传递给Channel角色,在本例中,ClientThread对应Client角色
Channel:
Channel角色接收来自Client角色的Request角色,并将其传递给Worker角色,在本例中,Channel类对 应Channel角色
Worker:
Worker角色从Channel角色中获取Request角色,并执行其逻辑,当一项工作结束后,继续从Channel获 取另外的Request角色,本例中,WorkerThread类对应Worker角色
Request:
Request角色表示工作,Request角色中保存了工作的逻辑,本例中,Request类对应Request角色
Worker Thread模式的优点:
1:提高吞吐量
如果将工作交给其他线程,当前线程就可以处理下一项工作,称为Thread Per Message模式,由于启动新线程消耗时间,可以通过Worker Thread模式轮流和反复地使用线程来提高吞吐量
2:容量控制
Worker角色的数量在本例中可以传递参数指定
Worker角色越多,可以并发处理的逻辑越多,同时增加Worker角色会增加消耗的资源(可以认为多个变量,自然会占用资源喽),必须根据程序实 际运行环境调整Worker角色的数量
3:调用与执行的分离
Worker Thread模式和Thread Per Message模式一样,方法的调用和执行是分开的(方法的调用是invocation,方法的执行是execution,只是代表英文名称),因为我调用你,但是却是开个线程执行的,自然分开,且他也操作了等待,至此才算认为是一个线程池,而不是前面的"类似于前面的ForkJoinPool类的分开操作"
这样,可以:
提高响应速度
控制执行顺序(虽然是自动的,而不是手动操作顺序,即不需要一定的顺序,也就是"适用于操作顺序没有要求时",即这个控制是没有要求的意思,这里要注意哦),因为执行不受调用顺序的制约
可以取消和反复执行
进行分布式部署,通过网络将Request角色发送到其他Woker计算节点进行处理
Runnable接口的意义:
java.lang.Runnable 接口有时用作Worker Thread模式的Request角色,即可以创建Runnable接口的 实现类对象表示业务逻辑,然后传递给Channel角色
Runnable对象可以作为方法参数,可以放到队列中,可以跨网络传输,也可以保存到文件中,如此则Runnable对象不论传输到哪个计算节点,都可以执行
也就是将我们要操作的Request角色的保存内容设置为线程的对象(即Runnable),线程池就是这样的,所以每次提交基本都是对应的Runnable的对象,虽然也可以是Callable
多态的Request角色:
本案例中,ClientThread传递给Channel的只是Request实例,但是WorkerThread并不知道Request类 的详细信息
所以即使我们传递的是Request的子类给Channel,WorkerThread也可以正常执行execute方法,那么若子类进行改变,即通过Request的多态,可以增加任务的种类(或者考虑其他的工作,如给某个变量加1),而无需修改Channel角色和Worker角色
JUC包和Worker Thread模式:
1:ThreadPoolExecutor类
java.util.concurrent.ThreadPoolExecutor 类是管理Worker线程的类,可以轻松实现Worker Thread模式
2:通过 java.util.concurrent 包创建线程池
java.util.concurrent.Executors 类就是创建线程池的工具类
总结:看成线程池即可
Future模式:
Future的意思是未来,假设由一个方法需要长时间执行才能获取结果,则一般不会让调用的程序等待,而是先 返回给它一张"提货卡",获取提货卡并不消耗很多时间,该"提货卡"就是Future角色
获取Future角色的线程稍后使用Future角色来获取运行结果,也就是说,你可以等下获取,很明显现在说的就是类似于前面的ForkJoinPool类了
示例程序:

在这里插入图片描述

package main16;

/**
 *
 */
public interface Data {
    String getContent();
}

package main16;

/**
 *
 */
public class FutureData implements Data {
    private RealData realData = null;
    private boolean ready = false;

    public synchronized void setRealData(RealData realData) {
        // balking,如果已经准备好,就返回
        if (ready) {
            return;
        }
        this.realData = realData;
        this.ready = true;
        notifyAll();
    }

    @Override
    public synchronized String getContent() {
        // guarded suspension
        while (!ready) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return realData.getContent();
    }
}

package main16;

/**
 *
 */
public class Host {
    public Data request(final int count, final char c) {
        System.out.println("\trequest(" + count + ", " + c + ") 开始");
        // 创建FutureData对象
        final FutureData future = new FutureData();
        // 启动新线程,创建RealData对象
        new Thread() {
            @Override
            public void run() {
                RealData realData = new RealData(count, c);
                future.setRealData(realData);
            }
        }.start();
        System.out.println("\trequest(" + count + ", " + c + ") 结束");
        // 返回提货单
        return future;
    }
}


package main16;

/**
 *
 */
public class Main {
    public static void main(String[] args) {
        Host host = new Host();
        Data data1 = host.request(10, 'A');
        Data data2 = host.request(20, 'B');
        Data data3 = host.request(30, 'C');

        System.out.println("等待一会⼉再获取结果");

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

        System.out.println("data1 = " + data1.getContent());
        System.out.println("data2 = " + data2.getContent());
        System.out.println("data3 = " + data3.getContent());
    }
}

package main16;

/**
 *
 */
public class RealData implements Data{
    private final String content;

    public RealData(int count, char c) {
        System.out.println("\t组装RealData(" + count + ", " + c + ") 开始");
        char[] buffer = new char[count];
        for (int i = 0; i < count; i++) {
            buffer[i] = c;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("\t\t组装RealData(" + count + ", " + c + ") 结束");
        this.content = new String(buffer);
    }

    @Override
    public String getContent() {
        return content;
    }

}

通过上面的观察,的确实现了等待他的结果,其中由于锁的对象是不同的,所以对应的notifyAll();只操作对应的一个,即的确是等待结果,也就与ForkJoinPool类似(他由于是二分开的(前面是二分),一般是难测试出来,实际上是这样,你可以自己测试)
流程图:

在这里插入图片描述

Future模式中的角色:
Client(请求者):
Client角色向Host角色发出请求,并立即接收到请求的处理结果(也就是Future角 色)
Client角色不必知道返回值是RealData还是Future角色,只需要稍后通过VirtualData角色来操作返回即可,本案例中,对应Main类
Host:
Host角色创建新的线程,由新线程创建RealData角色,同时,Host角色将Future角色(当做VirtualData角色)返回给Client角色,本案例中对应Host类
VirtualData(虚拟数据):
VirtualData角色是让Future角色与RealData角色具有一致性的角色,本案例中对应Data接口,所以对应可以通过Data直接得到
RealData(真实数据):
RealData角色是表示真实数据的角色,创建该对象需要花费很多时间,本案例中对应RealData类
Future:
Future角色是RealData角色的"提货单"(可以通过他得到返回数据),由Host角色传递给Client角色,对Client而言,Future角色就是VirtualData角色,当Client角色操作Future角色时线程会wait,直到RealData角色创建完成
Future角色将Client角色的操作委托给RealData角色, 本案例中,对应于FutureData类
很明显,这里只是单纯的操作返回,而之前的ForkJoinPool他在操作二分中,还需要操作返回,所以可能整体类的关系不同,但是对应的返回流程是类似的,而正是因为他需要考虑太多,所以看起来不一致也是正常的
要点:
1:使用Thread Per Message模式,可以提高程序响应性,但是不能获取结果,Future模式也可以提高程序 响应性,还可以获取处理结果,而ForkJoinPool相当于Worker Thread模式(补充,自然包含分开了,线程池自身就算分开的,只不过只是一次而已,但是既然是模式,自然可以继续操作,所以包含分开)和Future模式的结合,但是他主要考虑ForkJoinPool
2:利用Future模式异步处理特性,可以提高程序吞吐量,虽然并没有减少业务处理的时长(需要继续结合分开,不是单纯的线程创建了,而是创建线程进行二分任务,属于分开的一种),但是如果考虑 到I/O,当程序进行磁盘操作时,CPU不只是处于等待状态,即会有空闲时间处理其他的任务(这里中间可以进行操作,稍后获取就行,而不用等待你完成),并且他本身也是异步,所以自然会提高效率
3:"准备返回值"和"使用返回值"的分离(上面2中的空闲时间)
4:如果想等待处理完成后获取返回值,还可以考虑采用回调处理方式,即,当处理完成后(最后),由Host角色启 动的线程调用Client角色的方法(可以自己定义),进行结果的处理,此时Client角色中的方法需要线程安全地传递返回 值
JUC包与Future模式:
java.util.concurrent包提供了用于⽀持Future模式的类和接口
java.util.concurrent.Callable接口将"返回值的某种处理调用"抽象化了,Callable接口声明了call方法,call方 法类似于Runnable的run方法,但是call方法有返回值,Callable表示Callable接口的call方法返回值类型 为泛型,自然可以是String类型
java.util.concurrent.Future接口相当于本案例中的Future角色,Future接口声明了get方法来获取结果,但是 没有声明设置值的方法,设置值的方法需要在Future接口的实现类中声明,Future表示"Future接口的get方法返回值类型可以是String类型”,除了get方法,Future接口还声明了用于中断运行的cancel方法
java.util.concurrent.FutureTask类是实现了Future接口的标准类,FutureTask类声明了用于获取值的get方 法、用于中断运行的cancel方法、用于设置值的set方法,以及用于设置异常的setException方法,由于FutureTask类实现了Runnable接口,还声明了run方法
Callable、Future、FutureTask的类图:

在这里插入图片描述

总结:看成ForkJoinPool(没有二分的)即可
在多线程设计模式中,只需要看懂对应的代码流程即可,具体的说明可以大致过一下
至此多线程设计模式说明完毕
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值