Java异步编程

经典泡茶

从经典的泡茶案例说起,洗茶壶、洗茶具和烧开水被认定为比较耗时的操作,在下面的程序中假定洗茶壶需要2秒、洗茶具需要3秒、烧开水需要5秒(请忽略下图比例问题)通过jdk或第三方jar包中的类与接口来实现“异步泡茶”。

异步阻塞join

join()是线程类中的一个实例方法,它会阻塞当前执行该方法的线程,待目标线程(即调用该方法的线程对象所对应的线程,说起来有些拗口)执行完毕后再执行。下面的代码共有三个线程,主线程、烧水线程及清洗线程,主线程负责触发烧水线程和清洗线程启动,烧水线程和清洗线程并发的执行,待其执行完毕后主线程即可泡茶喝。

public class JoinDemo {
    public static void main(String[] args) {
        long begin = System.currentTimeMillis();
        Thread t1 = new Thread(new wash(), "清洗线程");
        Thread t2 = new Thread(new water(), "烧水线程");
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
            System.out.println("泡茶喝");
            System.out.println(System.currentTimeMillis() - begin);  //5018
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
	//清洗线程
    static class wash implements Runnable {
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + "开始洗茶壶");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "开始洗茶杯");
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "都洗好了!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
	//烧水线程
    static class water implements Runnable {
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + "开始烧水");
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName() + "水开了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

异步阻塞Future

Java中有一个Callable接口,和Runnable接口一样Callable接口也是提供一个可执行的任务给线程执行,Runnable接口使用的最大不便是它的run方法没有返回值以及无法抛出异常,Callable接口更为强大一点,它的call方法可以抛出异常并且是有返回值的。但是Callable接口的实现类对象并不能像Runnable接口的对象一样直接送到线程的构造方法中,它需要借助FutureTask类将自己“包装成一个Runnable”送进构造方法中,FutureTask类实现了Runnable和Future两个接口,前者让它可以作为Runnable实现类直接被送进线程Thread的构造方法中,后者为它提供了一系列把握任务生命周期的方法。

public class FutureDemo {
    public static void main(String[] args) {
        //使用FutureTask包装一下
        FutureTask<Boolean> washTarget = new FutureTask<Boolean>(new wash());
        FutureTask<Boolean> waterTarget = new FutureTask<Boolean>(new water());
        Thread t1 = new Thread(washTarget, "清洗线程--");
        Thread t2 = new Thread(waterTarget, "烧水线程--");
        t1.start();
        t2.start();
        try {
            //获取任务执行结果
            Boolean washBool = washTarget.get();
            Boolean waterBool = waterTarget.get();
            if (washBool && waterBool){			//全部成功再泡茶
                System.out.println("泡茶喝");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    static class wash implements Callable<Boolean>{
        public Boolean call() throws Exception {
            try {
                System.out.println(Thread.currentThread().getName()+"开始洗茶壶");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+"开始洗茶杯");
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName()+"都洗好了!");
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    }
    static class water implements Callable<Boolean>{
        public Boolean call() throws Exception {
            try {
                System.out.println(Thread.currentThread().getName()+"开始烧水");
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName()+"水开了");
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    }
}

Callable是一个泛型接口,其call方法返回值是其泛型类型,call方法的返回值会保存在FutureTask类中的一个成员变量outcome中,并可以通过get()方法获取执行结果。值得注意的是:get()方法被调用后同样会阻塞当前线程直至任务执行完毕,届时get方法会返回执行结果或抛出一个异常。

上面的代码中只是用了get方法,这是最常用的方法,但Future接口提供的方法还有很多。

异步回调Guava

        <dependency>
            <groupId>com.vaadin.external.google</groupId>
            <artifactId>guava</artifactId>
            <version>16.0.1.vaadin1</version>
        </dependency>

Guava是谷歌提供的一个Java工具包,使用Guava实现异步非阻塞的“泡茶”主要需要借助ListenableFutureFutureCallback两个接口。ListenableFuture扩展了Future接口,它新增了一个addListener(Runnable r,Executor e)方法,这个方法可以为任务绑定一个监听器用于执行回调函数,但实际编程中通常使用Futures工具类中的静态方法addCallback(ListenableFuture<V> future, FutureCallback<? super V> callback)方法完成任务和回调函数的绑定。FutureCallback接口即为上述提到的回调函数,需要重写其中的void onSuccess(V var1)void onFailure(Throwable var1)两个方法,前者在任务顺利执行完毕后被调用,入参是任务执行结果;后者在任务抛出异常后被调用,入参是所抛出的异常。

public class GuavaDemo {
    //两个标志
    static boolean warterOk = false;
    static boolean cupOk = false;

    public static void main(String[] args) {
        Callable<Boolean> wash = new Wash();
        Callable<Boolean> water = new Water();
        final ExecutorService jPool = Executors.newFixedThreadPool(10);
        ListeningExecutorService gPool = MoreExecutors.listeningDecorator(jPool);
        ListenableFuture<Boolean> washFuture = gPool.submit(wash);
        ListenableFuture<Boolean> waterFuture = gPool.submit(water);
        //回调函数
        Futures.addCallback(washFuture, new FutureCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean aBoolean) {
                if (aBoolean) {
                    cupOk = true;
                }
            }

            @Override
            public void onFailure(Throwable throwable) {
                System.out.println("清洗失败");
            }
        });
        
        Futures.addCallback(waterFuture, new FutureCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean aBoolean) {
                if (aBoolean)
                    warterOk = true;
            }

            @Override
            public void onFailure(Throwable throwable) {
                System.out.println("烧水失败!");
            }
        });
        //
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println("看书呢。。。");
                if (warterOk && cupOk) {
                    System.out.println("泡茶喝。。。");
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        jPool.shutdown();
    }

    static class Wash implements Callable<Boolean> {
        public Boolean call() throws Exception {
            try {
                System.out.println(Thread.currentThread().getName() + "开始洗茶壶");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "开始洗茶杯");
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "都洗好了!");
            } catch (InterruptedException e) {
                return false;
            }
            return true;
        }
    }

    static class Water implements Callable<Boolean> {
        public Boolean call() throws Exception {
            try {
                System.out.println(Thread.currentThread().getName() + "开始烧水");
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName() + "水开了");
            } catch (InterruptedException e) {
                return false;
            }
            return true;
        }
    }
}

上面的代码中加入了一个新的行为“看书”为了体现出这是一个非阻塞的操作,同时新增了两个布尔成员,在回调函数中通过改变它们的值为true来告知主线程清洗或烧水已经成功。

参考书籍

尼恩《Netty、Redis、Zookeeper高并发实战》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值