多线程

多线程(上)

基本概念:程序、进程、线程

  • 程序(program)是为了完成特定任务、用某种语言编写地一组指令的集合。即指一段静态的代码,静态对象。
  • 进程(process)是程序的依次执行过程,或是正在运行的一个程序。是一个动态的工程:有它自身的产生、存在和消亡的过程。-----生命周期
    • 程序是静态的,进程是动态的
    • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
  • 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
    • 若一个进程同一时间并行执行多个线程,就是支持多线程的
    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
    • 一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的遍历狼和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患。
  • 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。如果发生异常,会影响主线程。
  • 并行与并发
    • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
    • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
      image
      image
  • 多线程的创建,方式一:继承于Thread类
    1. 创建一个继承于Thread类的子类
    2. 重写Thread类的run()-->将此线程执行的操作声明在run()中
    3. 创建Thread类的子类的对象
    4. 通过此对象调用start()
//例:遍历100以内的所有的偶数
//1.创建一个继承于Thread类的子类
class Mythread extends Thread{
    //2.重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }

        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类的对象
        Mythread t1 = new Mythread();
        //4.通过此对象调用start():4.1启动当前线程 4.2调用当前线程的run()
        //问题一:我们不能通过直接调用run()的方式启动线程
//        t1.run();
        t1.start();
        //问题二:再启动一个线程遍历100内的偶数,错误,不可以还让已经start()的线程去执行。会报IllegalThreadStateException异常
        //我们需要重新创建一个线程的对象
//        t1.start();
        Mythread t2 = new Mythread();
        t2.start();
//创建Thread匿名子类的方式
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("匿名子类方式");
                }
            }
        }.start();
        //如下操作在main方法中执行
        for (int i = 0; i < 100; i++) {
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }

        }
    }
}
  • Thread中常用方法:

    1. start():启动当前线程;调用当前线程的run()
    2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    3. currentThread():静态方法,返回执行当前代码的线程
    4. getName():获取当前线程的名字
    5. setName():设置当前线程的名字
    6. yield():释放当前cpu的执行权
    7. join():在线程a中调用线程b的join(),此时线程啊就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
    8. stop():已过时,当执行完此方法时,强制结束当前线程
    9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程时阻塞状态。
    10. isAlive():判断当前线程是否还存活
  • 线程的调度

    1. 调度策略
      • 时间片
      • 抢占式:高优先级的线程抢占CPU
    2. Java的调度方法
      • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
      • 对高优先级,使用有限调度的抢占式策略
    3. 线程的优先级等级
      • MAX_PRIORITY:10
      • MIN_PRIORITY:1
      • NORM_PRIORITY:5
      • 涉及的方法:
        • getPriority():返回线程优先值
        • setPriority(int newPriority):改变线程的优先级
      • 说明:
        • 线程创建时继承父线程的优先级
        • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
        • 高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的抢矿下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
  • 创建多线程的方式二:实现Runnable接口

    1. 创建一个实现了Runnable接口的类
    2. 实现类去实现Runnable中的抽象方法:run()
    3. 创建实现类的对象
    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    5. 通过Thread类的对象调用start()

/*
* 创建多线程的方式二:实现Runnable接口
	1. 创建一个实现了Runnable接口的类
	2. 实现类去实现Runnable中的抽象方法:run()
	3. 创建实现类的对象
	4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
	5. 通过Thread类的对象调用start()
 */
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
    //实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
        //3.创建实现类的对象
        MThread mThread = new MThread();
        //4.将此对象传入到Thread类的构造中,创建Thread的对象
        Thread thread = new Thread(mThread);
        //5.通过Thread类的对象调用start 5.1 启动线程 5.2 调用当前线程的run--->调用了Runnable类型的target的run方法
        thread.setName("线程一");
        thread.start();

        //再启动一个线程,遍历100以内的偶数
        Thread t2 = new Thread(mThread);
        t2.setName("线程二");
        t2.start();
    }
}
  • 比较创建线程的两种方式:
    • 开发中:优先选择:实现Runnable接口的方式
    • 原因:1.实现的方式没有类的单继承性的局限性 2.实现的方式更适合来处理多个线程有共享数据的情况
    • 联系:public class Thread implements Runnable
    • 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

创建线程的第三种方法:

使用Callable接口创建线程

/*创建一个类实现Callable接口后面可加泛型规定返回值的类型*/
class BooleanCallableTask implements Callable<Boolean>{

    @Override
    public Boolean call() throws Exception {
        System.out.println("12345");
        return false;
    }
}
class IntegerCallable implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0;i<100;i++){
            sum = sum+i;
        }
        return sum;
    }
}
public class synchronizedTest {
    public static void main(String[] args) {

       /*声明一个callable对象,使用futureTask进行处理callable以及其中的返回结果*/
        BooleanCallableTask callable = new BooleanCallableTask();
        FutureTask futureTask = new FutureTask(callable);
        new Thread(futureTask).start();
        try {
            Object o = futureTask.get();
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }



        /*通过线程池处理callable创建的线程,以及处理结果*/
        ExecutorService executorService = Executors.newCachedThreadPool();
        IntegerCallable integerCallable = new IntegerCallable();
        Future<Integer> future = executorService.submit(integerCallable);
        executorService.shutdown();
        try {
            Integer integer = future.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }


}

关于Future接口

Future表示异步计算的结果。提供了检查计算是否完成、等待其完成以及检索计算结果的方法。结果只能在计算完成后使用方法get检索,必要时阻塞,直到它准备好。取消是通过cancel方法执行的。提供了额外的方法来确定任务是正常完成还是被取消。一旦计算完成,就不能取消计算。如果您想使用Future来取消可取消性但不提供可用的结果,您可以声明Future<?>形式的类型并返回null作为底层任务的结果。

当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。从而我们可以使用Future对象。将Future视为保存结果的对象-它可能暂时不保存结果,将来会保存(一旦Callable返回)。Future基本上是主线程可以跟踪进度以及其他线程结果的一种方式。要实现此接口必须重写5种方法(直接上代码):

    /*Future 尝试取消此任务的执行。如果任务已完成、已被取消或由于某些其他原因无法取消,则此尝试将失败。如果成功,
	并且在调用cancel时此任务尚未启动,则此任务不应该运行。如果任务已经开始,则mayInterruptIfRunning参数确定是否应该中断执行该任务的线程以尝试停止该任务。
此方法返回后,对isDone的后续调用将始终返回true 。如果此方法返回true ,则对isCancelled的后续调用将始终返回true 。*/
    public boolean cancel(boolean mayInterruptIfRunning);

    /*如果此任务在正常完成之前被取消,则返回true */
    public boolean isCancelled();

    /*如果此任务完成,则返回true 。完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法都将返回true 。*/
    public boolean isDone();

    /*如有必要,等待计算完成,然后检索其结果*/
    public Object get();

    /*如有必要,最多等待给定时间以完成计算,然后检索其结果(如果可用)*/
    public Object get(long timeout, TimeUnit unit);

Future只能配合线程池使用,作为主线程向线程池提交任务后的返回值,但需要注意的是这时主线程并不会阻塞,只有当需要线程执行结果时调用Future的get方法时,才可能会阻塞。这就是Future强大的之处,
使用方法上边已经注明:

/*通过线程池处理callable创建的线程,以及处理结果*/
        ExecutorService executorService = Executors.newCachedThreadPool();
        IntegerCallable integerCallable = new IntegerCallable();
        Future<Integer> future = executorService.submit(integerCallable);
        executorService.shutdown();
        try {
            Integer integer = future.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

关于FutureTask对象

介绍:当一个线程需要等待另一个线程把某个任务执行完成后它才能继续执行,此时可以使用FutureTask。假设有多个线程执行若干任务,每个任务最多执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行。
java库具有的FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能结合在一起,可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread,因此,间接地使用Callable创建线程。

FutureTask有以下7种状态:
NEW:表示一个新的任务,初始状态
COMPLETING:当任务被设置结果时,处于COMPLETING状态,这是一个中间状态
NORMAL:表示任务正常结束
EXCEPTIONAL:表示任务因异常结束
CANCELLED:任务还未执行就调用了cancel(true)方法,任务处于CANCELLED
INTERRUPTING:当任务调用cancel(true)中断程序时,任务处于INTERRUPTING状态,这是一个中间状态
INTERRUPTED:任务调用cnacel(true)中断程序时会调用interrupt()方法中断线程运行,任务状态由INTERRUPTING转换未INTERRUPTED

Future任务的运行状态,最初为NEW。运行状态仅在set、setException和cancel方法中转换为终端状态。在完成过程中,状态可能呈现出瞬时值INTERRUPTING(仅在中断运行程序以满足cancel(true)的情况下)或者COMPLEING(在设置结果时)状态时。从这些中间状态到最终状态的转换使用成本更低的有序/延迟写,因为值是统一的,需要进一步修改。
state:表示当前任务的运行状态,FutureTask的所有方法都是围绕state开展的,state声明为volatile,保证了state的可见性,当对state进行修改时所有的线程都会看到。

可能出现的状态过渡:
1、NEW -> COMPLETING -> NORMAL: 正常结束
2、NEW -> COMPLETING -> EXCEPTION: 异常结束
3、NEW -> CANCELLED : 任务被取消
4、NEW -> INTERRUPTING -> INTERRUPTED: 任务出现中断

关于多线程的继承关系:

Thread类 实现 Runnable接口
RunnableFuture接口 继承于 Runnable接口
FutureTask类 实现 RunnableFuture 接口

关于FutureTask的使用:

public class FutureTaskTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
        new Thread(futureTask,"线程计算0-100的和").start();
        while (!futureTask.isDone()){
            System.out.println(Thread.currentThread().getName()+":waiting");
        }
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName()+":结束了");
    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i< 100; i++){
            sum = sum +i;
            System.out.println(Thread.currentThread().getName()+"中的第"+i+"次循环");
        }
        return sum;
    }
}

通过FutureTask可以根据某一线程的完成状态来决定之后的操作。在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。

一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞get方法。一旦计算完成,就不能重新开始或者取消计算。get方法获取结果只有在计算完时获取,否则会一直阻塞直到任务变为完成状态然后会返回结果或者抛出异常。
get只计算一次,因此,get()方法放到最后。

关于ExecutorService的创建方式

ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。

1、ExecutorService的创建方式

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

所有线程池最终都是通过这个对象来创建的。
corePoolSize:核心线程数一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。.
maximumPoolSize:最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程。如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用资源。
keepAliveTime:也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对与非核心线程
unit:时间单位,TimeUnit.SECONDS等。
workQueue:任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务
threadFactory:线程工厂,用于创建线程
handler:当线程边界和队列容量已经达到最大时,用于处理阻塞的程序。

2、线程池的类型

1、可缓存线程池:

ExecutorService cachePool = Executors.newCachedThreadPool();

创建方式:

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

创建的皆为非核心线程,而且最大线程数为Integer的最大值,空闲线程存活时间是1分钟。如果有大量耗时任务,则不适用与该创建方式。它只适用于生命周期短的任务
2、单线程池
顾名思义,也就是创建一个核心线程。

ExecutorService singlePool = Executor.newSingleThreadExecutor();

创建方式:

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

只用一个线程来执行任务,保证任务按FIFO顺序执行(先进先出)
3.固定线程数线程池

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

也就是创建固定数量可复用的线程数,来执行任务。当线程达到最大核心线程数,则加入队列等待有空闲线程时再执行。

4、固定线程数,支持定时和周期性任务

ExecutorService scheduledPool = Executors.newScheduledThreadPool(5);

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

可用于替代handler.postDelay和Timer定时器等掩饰和周期性任务

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedTate(Runnable command,long initialDelay,long period,TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);

scheduleAtFixedRate:创建并执行一个在给定初始延迟后的定期操作,也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后下一个任务执行,接着在 initialDelay + 2 * period 后执行,依此类推。

sheduleWithFixedDelay:创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟,即总时间是(initialDelay + period)*n

4、手动创建线程池

private ExecutorService pool = new ThreadPoolExecutor(3,10,10L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(512),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

5、使用线程池:
execute(Runnable)
方法 execute(Runnable) 接收一个 java.lang.Runnable 对象作为参数,并且以异步的方式执行它。

submit(Runnable)
方法 submit(Runnable) 同样接收壹個 Runnable 的实现作为参数,但是会返回壹個 Future 对象。这個 Future 对象可以用于判断 Runnable 是否结束执行。(Future.isDOne())

submit(Callable)
方法 submit(Callable) 和方法 submit(Runnable) 比较类似,但是区别则在于它们接收不同的参数类型。Callable 的实例与 Runnable 的实例很类似,但是 Callable 的 call() 方法可以返回一个结果。方法 Runnable.run() 则不能返回结果。

inVokeAny()
方法 invokeAny() 接收一个包含 Callable 对象的集合作为参数。调用该方法不会返回 Future 对象,而是返回集合中某一个 Callable 对象的结果,而且无法保证调用之后返回的结果是哪一个 Callable,只知道它是这些 Callable 中一个执行结束的 Callable 对象。
如果一个任务运行完毕或者抛出异常,方法会取消其它的 Callable 的执行。
Callable 的返回值可以从方法 submit(Callable) 返回的 Future 对象中获取。

invokeAll()
方法 invokeAll() 会调用存在于参数集合中的所有 Callable 对象,并且返回一个包含 Future 对象的集合,你可以通过这个返回的集合来管理每个 Callable 的执行结果。
需要注意的是,任务有可能因为异常而导致运行结束,所以它可能并不是真的成功运行了。但是我们没有办法通过 Future 对象来了解到这个差异。

简单举一个例子(如何使用线程池):

/*Runnable线程,用于休眠5秒后结束*/
class TestRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+",5秒后结束+"+ LocalDateTime.now().toLocalTime());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"结束。"+LocalDateTime.now().toLocalTime());
    }
}

/*callable用于返回1-100相加的结果*/
class TestCallAble implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i=0;i<=100;i++){
            sum = sum+i;
        }
        System.out.println(Thread.currentThread().getName()+"执行结果:"+sum);
        return sum;
    }
}
public class ExecutorTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*创建线程池,在线程池中添加线程任务,关闭线程池*/
        ExecutorService executorService = new ThreadPoolExecutor(1,5,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
        //execute的用法
        //执行其中一个线程任务
        executorService.execute(new TestRunnable());
        /*submit(Runnable)的用法:判断Runnable是否结束运行*/
        Future<?> submit = executorService.submit(new TestRunnable());
        if (submit.isDone()){

            System.out.println(Thread.currentThread().getName()+",运行结果"+submit.get()+":"+ LocalDateTime.now().toLocalTime());/*返回结果为null*/
        }
        Future<Integer> submit1 = executorService.submit(new TestCallAble());
        System.out.println(Thread.currentThread().getName()+",运行结果"+submit1.get()+":"+LocalDateTime.now());
        try {
            //固定时间后关闭线程池,终止线程中的任务
            executorService.shutdown();
            /*如果线程等待时间超过awaitTermination中的时间,使用shutdownNow进行终止线程*/
            if (!executorService.awaitTermination(6,TimeUnit.SECONDS)){
                //超时的时候向线程池中的所有线程发生中断interrupted
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(e);
            /*若有任务未完成,捕获异常,关闭线程池*/
            executorService.shutdownNow();
        }

        System.out.println(Thread.currentThread().getName()+"结束"+LocalDateTime.now().toLocalTime());
    }

}

关于CompletionService

当我们有一个批量执行多个异步任务的业务,并且需要调用get()获取执行结果的时候,代码如下:

Future f1 = executor.submit(c1);
f1.get();
Future f2 = executor.submit(c2);
f2.get();

此时,f1.get()在获取成功之前会被阻塞,会阻塞c2的执行,严重影响了执行效率。
CompletionService与ExecutorService类似都可以用来执行线程池的任务,ExecutorService继承了Executor接口,而CompletionService则是一个接口,那么为什么CompletionService不直接继承Executor接口呢?主要是Executor的特性决定的,Executor框架不能完全保证任务执行的异步性,那就是如果需要实现任务(task)的异步性,只要为每个task创建一个线程就实现了任务的异步性。代码往往包含new Thread(task).start()。这种方式的问题在于,它没有限制可创建线程的数量(在ExecutorService可以限制),不过,这样最大的问题是在高并发的情况下,不断创建线程异步执行任务将会极大增大线程创建的开销、造成极大的资源消耗和影响系统的稳定性。另外,Executor框架还支持同步任务的执行,就是在execute方法中调用提交任务的run()方法就属于同步调用。

一般情况下,如果需要判断任务是否完成,思路是得到Future列表的每个Future,然后反复调用其get方法,并将timeout参数设为0,从而通过轮询的方式判断任务是否完成。为了更精确实现任务的异步执行以及更简便的完成任务的异步执行,可以使用CompletionService。

使用Future获取线程池的执行结果案例:

ExecutorService pool = Executors.newFixedThreadPool(4);
 
List<Future<String>> futures = new ArrayList<Future<String>>(10);
 
for(int i = 0; i < 10; i++){
   futures.add(pool.submit(所要执行的方法);
}
 
for(Future<String> future : futures){
   String result = future.get();
 
   //Compute the result
}
 
pool.shutdown();

CompletionService实际上可以看做是Executor和BlockingQueue的结合体。CompletionService在接收到要执行的任务时,通过类似BlockingQueue的put和take获得任务执行的结果。CompletionService的一个实现是ExecutorCompletionService,ExecutorCompletionService把具体的计算任务交给Executor完成。

在实现上,ExecutorCompletionService在构造函数中会创建一个BlockingQueue(使用的基于链表的无界队列LinkedBlockingQueue),该BlockingQueue的作用是保存Executor执行的结果。当计算完成时,调用FutureTask的done方法。当提交一个任务到ExecutorCompletionService时,首先将任务包装成QueueingFuture,它是FutureTask的一个子类,然后改写FutureTask的done方法,之后把Executor执行的计算结果放入BlockingQueue中。QueueingFuture的源码如下:

private class QueueingFuture extends FutureTask<Void> {
       QueueingFuture(RunnableFuture<V> task) {
           super(task, null);
           this.task = task;
       }
       protected void done() { completionQueue.add(task); }
       private final Future<V> task;
   }

CompletitionService案例:

ExecutorService threadPool = Executors.newFixedThreadPool(4);
CompletionService<String> pool = new ExecutorCompletionService<String>(threadPool);
 
for(int i = 0; i < 10; i++){
   pool.submit(new StringTask());
}
 
for(int i = 0; i < 10; i++){
   String result = pool.take().get();
 
   //Compute the result
}
 
threadPool.shutdown();

该对象所包含的方法:
poll():检索并删除代表下一个完成任务的Future,或者如果没有,则null 。
submit():提交一个返回值的任务以供执行,并返回一个表示该任务待处理结果的 Future。完成后,可以采取或轮询此任务。
takle():检索并删除代表下一个已完成任务的 Future,如果还没有,则等待。

关于CompletableFuture3

常用方法:

依赖关系:

  • thenApply():把前面任务的执行结果,交给后面的Function
  • thenCompose():用两个有依赖关系的任务,结果由第二个任务返回
    and集合关系:
  • thenCombine():合并任务,有返回值
  • thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
  • runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)
    or聚合关系
  • applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
  • acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
  • runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)
    并行执行
  • allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
  • anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture
    结果处理
  • whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作
  • exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成

异步操作:
CompletableFuture提供了四个静态方法来创建一个异步操作:

public static CompletableFuture<Void> runAsync(Runnable runnable)
//自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

这四个方法的区别:

  • runAsync() 以Runnable函数式接口类型为参数,没有返回结果,supplyAsync() 以Supplier函数式接口类型为参数,返回结果类型为U;Supplier接口的 get()是有返回值的(会阻塞)
  • 使用没有指定Executor的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
  • 默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置ForkJoinPool线程池的线程数)。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

获取结果:
join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)

结果处理:
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常

示例:

public class CompletableFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Runnable runnable = ()-> System.out.println("无返回值的异步任务");
        CompletableFuture.runAsync(runnable);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
            System.out.println("有返回值的异步任务");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello world";
        });
        String result = future.get();
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (new Random().nextInt(10)%2==0){
//                int i = 12/0;
            }
            System.out.println("执行结束");
            return "test";
        });
        /*任务完成或异常方法完成时执行该方法,如果出现了异常,任务结果为null*/
        future1.whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                System.out.println(s +"执行完成");
            }
        });
        /*出现异常时先执行该方法*/
        future1.exceptionally(new Function<Throwable, String>(){
            @Override
            public String apply(Throwable throwable) {
                System.out.println("执行失败"+throwable.getMessage());
                return "异常xxx";
            }
        });
        future1.get();

        ExecutorService poolExecutor = new ThreadPoolExecutor(1,3,3,TimeUnit.SECONDS,new SynchronousQueue<>());
        /*指定线程池运行 CompletableFuture*/
        CompletableFuture.supplyAsync(()->{
            return 123;
        },poolExecutor);
    }
}

应用场景

注意:方法不以Async结尾,意味着使用相同的线程执行,而Async结尾,可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。

结果转换

将上一段任务的执行结果作为下一阶段任务的入参 参与 重新计算,产生新的结果。

  • thenApply
    thenApply 接收一个函数作为参数,使用该函数处理上一个CompletableFuture调用的结果,并返回一个具有处理结果的Future对象。
    常见使用:
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)

具体使用:

CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("第一次运算结果:100");
            return 100;
        }).thenApplyAsync(number -> {
            return number * 3;
        }, poolExecutor);
        System.out.println("第二次运算结果:"+ completableFuture.get());

输出结果:
第一次运算结果:100
第二次运算结果:300

  • thenCompose
    thenCompose的参数为一个返回CompletableFuture实例的函数,该函数的参数是先前计算步骤的结果。

常用方法:

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;

具体使用:

 CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(30);
                System.out.println("生成的第一个随机数" + number);
                return number;
            }
        }).thenCompose(new Function<Integer, CompletionStage<Integer>>() {
            @Override
            public CompletionStage<Integer> apply(Integer integer) {
                return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int number = integer * 2;
                        System.out.println("第二次运算" + number);
                        return number;
                    }
                });
            }
        });
        System.out.println("获取的结果:"+completableFuture1.get());

thenApply 和 thenCompose的区别:
thenApply转换的是泛型中的类型,返回的是同一个CompletableFuture;
thenCompose将内部的CompletableFuture调用展开来并使用上一个CompletableFutre调用的结果在下一步的CompletableFuture调用中进行运算,是生成一个新的CompletableFuture。

结果消费:

与结果处理和结果转换系列函数返回一个新的CompletableFuture不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。

根据对结果的处理方式,结果消费函数又可以分为下面三大类:

  • thenAccept():对单个结果进行消费
    观察该系列函数的参数类型可知,它们是函数式接口Consumer,这个接口只有输入,没有返回值。
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
  • thenAcceptBoth():对两个结果进行消费
    thenAcceptBoth函数的作用是,当两个CompletionStage都正常完成计算的时候,就会执行提供的action消费两个异步的结果。
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);

示例:

CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        int number = new Random().nextInt(3) + 1;
        try {
            TimeUnit.SECONDS.sleep(number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务1结果:" + number);
        return number;
    }
});

CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        int number = new Random().nextInt(3) + 1;
        try {
            TimeUnit.SECONDS.sleep(number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务2结果:" + number);
        return number;
    }
});

futrue1.thenAcceptBoth(future2, new BiConsumer<Integer, Integer>() {
    @Override
    public void accept(Integer x, Integer y) {
        System.out.println("最终结果:" + (x + y));
    }
});

thenRun():不关心结果,只对结果执行Action
thenRun也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun会在上一阶段 CompletableFuture计算完成的时候执行一个Runnable,而Runnable并不使用该CompletableFuture计算的结果。
常用方法:

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);

结果组合

  • thenCombine:合并两个线程任务的结果,并进一步处理。
    常用方法:
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);

public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);

public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor);

代码示例:

CompletableFuture<Integer> future1 = CompletableFuture
    .supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int number = new Random().nextInt(10);
            System.out.println("任务1结果:" + number);
            return number;
        }
    });
CompletableFuture<Integer> future2 = CompletableFuture
    .supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int number = new Random().nextInt(10);
            System.out.println("任务2结果:" + number);
            return number;
        }
    });
CompletableFuture<Integer> result = future1
    .thenCombine(future2, new BiFunction<Integer, Integer, Integer>() {
        @Override
        public Integer apply(Integer x, Integer y) {
            return x + y;
        }
    });
System.out.println("组合后结果:" + result.get());

任务交互

线程交互指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理。
applyToEither
两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的转化操作。

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);

acceptEither
两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作。

常用方法:

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);

runAfterEither
两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行结果。

常用方法:

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);	

anyOf
anyOf() 的参数是多个给定的 CompletableFuture,当其中的任何一个完成时,方法返回这个 CompletableFuture。

常用方法:

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

allOf
allOf方法用来实现多 CompletableFuture 的同时返回。

常用方法:

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

image

image
image

posted @ 2022-05-14 18:50  生活的样子就该是那样  阅读(38)  评论(0编辑  收藏  举报