JUC并发编程第二章之CompletableFuture[加强版的线程]

文章目录

  • 1、 创建线程的几种方式
  • 2、 线程之间的通讯方式
    • 2.1、什么是多线程之前的通讯
    • 2.2、线程讯通之间的问题演示
    • 2.3、synchronized 解决讯通问题
    • 2.4、wait()、notify() 解决讯通问题
    • 2.5、Lock,Condtion 解决讯通问题
  • 2、 Future的优缺点
  • 3、CompletableFuture对Future的改进
    • 3.1、CompletableFuture的基本结构
    • 3.2、创建CompletableFuture四种方式
    • 3.3、CompletableFuture的流式调用
    • 3.4、CompletableFuture常见的用法

1、 创建线程的几种方式

new thread:

        //构造方法给指定的线程指定名称,推荐
        Thread thread = new Thread(t1) {
            @Override
            public void run() {
                //要执行的任务
            }
        };
        //启动线程
        thread.start();

new runnable:

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                //要执行的任务
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();

Future

        FutureTask<Integer> task = new FutureTask<>(() -> {
            System.out.println(执行的任务);
            return 100;
        });
        new Thread(task,t1).start();
        Integer integer = task.get();
        System.out.println(结果是+integer);

2、 线程之间的通讯方式

2.1、什么是多线程之前的通讯

其实就是多个线程之间在操作同一个资源,但是操作的动作不同
就比如下图,一个线程进行set方法给属性赋值,另一个线程get方法获取属性的值
而本章中要学习的就是:如何保证线程每写一个值,读线程便同步获取写入的那个值;
JUC并发编程第二章之CompletableFuture[加强版的线程]_第1张图片

2.2、线程讯通之间的问题演示

package com.disney;

class Res {
    public String name;
    public String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

/**
 * 写入的线程
 *
 * 当为偶数为男孩,奇数为女孩
 */
class InputThread extends Thread {

    private Res res;

    public InputThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        int count = 0;
        while (true){
            if (count==0){
               res.name="william";
               res.sex="boy";
            } else{
                res.name="tom";
                res.sex="girl";
            }
            //得到count+1的模
            count=(count+1)%2;
        }
    }
}

/**
 * 读取的线程
 */
class OutPutThread extends Thread{


    private Res res;

    public OutPutThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        while (true){
            System.out.println(res.name+"======"+res.getSex());
        }
    }
}


public class MainDemo{
    public static void main(String[] args) {
        Res res = new Res();
        OutPutThread outPutThread = new OutPutThread(res);
        outPutThread.start();

        InputThread inputThread = new InputThread(res);
        inputThread.start();
    }
}

输出结果

JUC并发编程第二章之CompletableFuture[加强版的线程]_第2张图片
根据现实的结果我们可以看出,本来我们是william和boy在一组进行设置 ,但是发现在进行读的时候,数据发生了交叉,以上的数据就是线程通讯不安全的表现,接下来讲解如何解决这个问题

2.3、synchronized 解决讯通问题

package com.disney;

class Res {
    public String name;
    public String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

/**
 * 写入的线程
 *
 * 当为偶数为男孩,奇数为女孩
 */
class InputThread extends Thread {

    private Res res;

    public InputThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        int count = 0;
        while (true){
            synchronized (res){

            if (count==0){
               res.name="william";
               res.sex="boy";
            } else{
                res.name="tom";
                res.sex="girl";
            }
            //得到count+1的模
            count=(count+1)%2;
            }

        }
    }
}

/**
 * 读取的线程
 */
class OutPutThread extends Thread{


    private Res res;

    public OutPutThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        while (true){
            synchronized (res) {
                System.out.println(res.name + "======" + res.getSex());
            }
        }
    }
}


public class MainDemo{
    public static void main(String[] args) {
        Res res = new Res();
        OutPutThread outPutThread = new OutPutThread(res);
        outPutThread.start();
        InputThread inputThread = new InputThread(res);
        inputThread.start();
    }
}

输出结果
JUC并发编程第二章之CompletableFuture[加强版的线程]_第3张图片
可以看出来的确姓名和性别之间的对应关系有了匹配,但是并不是符合 我们 输出william->toim->william-tom这样逐一变化的规律

2.4、wait()、notify() 解决讯通问题

wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。

• 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
• 如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
• 如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

package com.disney;

class Res {
    public String name;
    public String sex;
    public boolean flag =false;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

}

/**
 * 写入的线程
 *
 * 当为偶数为男孩,奇数为女孩
 */
class InputThread extends Thread {

    private Res res;

    public InputThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        int count = 0;
        while (true){
            synchronized (res){
                if (res.flag){
                    try {
                        res.wait();
                    } catch (InterruptedException e) {
                    }

                }
                if (count==0){
                    res.name="william";
                    res.sex="boy";
                } else{
                    res.name="tom";
                    res.sex="girl";
                }
                count=(count+1)%2;
                res.flag=true;
                //得到count+1的模
                res.notify();
            }
        }
    }
}

/**
 * 读取的线程
 */
class OutPutThread extends Thread{


    private Res res;

    public OutPutThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        while (true){
            synchronized (res) {
            if (!res.flag) {
                try {
                    res.wait();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
              }

                System.out.println(res.name + "======" + res.getSex());
                res.flag=false;
                res.notify();
            }
        }
    }
}


public class MainDemo{
    public static void main(String[] args) {
        Res res = new Res();
        OutPutThread outPutThread = new OutPutThread(res);
        InputThread inputThread = new InputThread(res);
        outPutThread.start();
        inputThread.start();
    }
}

2.5、Lock,Condtion 解决讯通问题

在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
Local的作用其实就类似synchronized 。但是因为wait是和synchronized 搭配使用,此时我们使用lock就需要一个能代替wait的方法,此时
Condition 的就诞生啦‘’

package com.disney;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Res {
    public String name;
    public String sex;
    public boolean flag =false;
    Lock lock  = new ReentrantLock();
    Condition condition = lock.newCondition();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

}

/**
 * 写入的线程
 *
 * 当为偶数为男孩,奇数为女孩
 */
class InputThread extends Thread {

    private Res res;

    public InputThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        int count = 0;
        while (true){
            res.lock.lock();
                if (res.flag){
                    try {
                        res.condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (count==0){
                    res.name="william";
                    res.sex="boy";
                } else{
                    res.name="tom";
                    res.sex="girl";
                }
                count=(count+1)%2;
                res.flag=true;
                //得到count+1的模
            res.condition.signal();
            res.lock.unlock();
            }
        }
    }

/**
 * 读取的线程
 */
class OutPutThread extends Thread{


    private Res res;

    public OutPutThread(Res res ) {
        this.res=res;
    }

    @Override
    public void run() {
        while (true){
            if (!res.flag) {
                res.lock.lock();
                try {
                    res.condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
              }

                System.out.println(res.name + "======" + res.getSex());
                res.flag=false;
                res.condition.signal();
            res.lock.unlock();

        }
    }
}


public class MainDemo{
    public static void main(String[] args) {
        Res res = new Res();
        OutPutThread outPutThread = new OutPutThread(res);
        InputThread inputThread = new InputThread(res);
        outPutThread.start();
        inputThread.start();
    }
}

2、 Future的优缺点

优点: Future配合线程池能够显著提高程序的执行效率

public static void main(String[] args) throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        FutureTask<String> stringFutureTask1 = new FutureTask<>(() -> {
            TimeUnit.SECONDS.sleep(5);
            return over;
        });
        executorService.submit(stringFutureTask1);


        FutureTask<String> stringFutureTask2 = new FutureTask<>(() -> {
            TimeUnit.SECONDS.sleep(3);
            return over;
        });
        executorService.submit(stringFutureTask2);

        FutureTask<String> stringFutureTask3 = new FutureTask<>(() -> {
            TimeUnit.SECONDS.sleep(3);
            return over;
        });
        executorService.submit(stringFutureTask3);
        System.out.println(stringFutureTask1.get());
        System.out.println(stringFutureTask2.get());
        System.out.println(stringFutureTask3.get());
        executorService.shutdown();
        long endTime = System.currentTimeMillis();
        System.out.println(耗时了+(endTime-startTime));
    }

输出结果:
over
over
over
耗时了5084

可以看到如果是串行输出,结果是5s+3s+3s的耗时;


  • 缺点1:Get 方法容易导致堵塞

从上面的程序,我必须得到stringFutureTask1 执行完后,主线程才能执行任务, 输出耗时时间,我们的期望是 stringFutureTask1在耗时5s的时间内,主线程忙其他事情, 并询问下stringFutureTask1 是否执行完毕, 如果执行完毕,则输出耗时时间;

  • 缺点2:Get 方法没有最大等待时间

比如我们最多只能等待5s, 但是如果get()方法执行10s的话, 则会影响我们的程序

  • 缺点3: isDone 容易导致cpu轮训空转
   public static void main(String[] args) throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        FutureTask<String> stringFutureTask1 = new FutureTask<>(() -> {
            TimeUnit.SECONDS.sleep(5);
            return over;
        });
        executorService.submit(stringFutureTask1);
        executorService.shutdown();

        while (true) {
            if (stringFutureTask1.isDone()){
                long endTime = System.currentTimeMillis();
                System.out.println(耗时了 + (endTime - startTime));
            }
        }
    }

3、CompletableFuture对Future的改进

对于真正的异步处理我们希望可以通过传入回调函数,在Future结束时自动调用该函数;这样我们就不用等待结果了;

3.1、CompletableFuture的基本结构

public class CompletableFuture implements Future, CompletionStage
  • CompletionStage 代表计算过程的一个阶段,一个阶段完成以后可能会触发另一个阶段;

  • 一个阶段的计算,可以是一个function,consumer或者runnable. 比如stage.thenApply(x->square(x)).thenAccept(x->system.out.print(x)).thenReturn(()->system.out.println())

  • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发;

3.2、创建CompletableFuture四种方式

  • runAsync 无返回值
返回值 具体方法
static CompletableFuture runAsync(Runnable runnable)
static CompletableFuture runAsync(Runnable runnable, Executor executor)

代码示例

public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },executorService);
        System.out.println(voidCompletableFuture.get());
    }
  • supplyAsync有返回值
返回值 具体方法
static CompletableFuture supplyAsync(Supplier supplier)
static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
 public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return over;
        }, executorService);
        System.out.println(stringCompletableFuture.get());<br></br>        System.out.println(stringCompletableFuture.jion());
    }

输出结果
pool-1-thread-1
over
  • 指的注意的是,join方法和get方法,都可以获取返回值,而join没有处理异常而已;他不会要求抛出异常

3.3、CompletableFuture的流式调用

public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return over;
        }, executorService).whenComplete((v, e) -> {
            System.out.println(hello +v);
            if (e == null) {
                System.out.println(没有异常,更新完成);
            }
        }).exceptionally(s -> {
            s.printStackTrace();
            System.out.println(异常了,主线程先忙其他事情);
            return null;
        });
        System.out.println(主线程工作);
    }

扩展: 函数值编程接口JUC并发编程第二章之CompletableFuture[加强版的线程]_第4张图片

3.4、CompletableFuture常见的用法

对计算结果进行合并

public static void main(String[] args) throws Exception {
        CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
            return 1;
        });
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            return 10;
        });

        CompletableFuture<Integer> integerCompletableFuture = task1.thenCombine(task2, (x, y) -> {
            return x + y;
        });
        System.out.println(integerCompletableFuture.get());
    }

你可能感兴趣的:(并发编程,java)