zoukankan      html  css  js  c++  java
  • 多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask

    CyclicBarrier

    接着讲多线程下的其他组件,第一个要讲的就是CyclicBarrier。CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到所有线程都达到了这个屏障时,再一起继续执行后面的动作。看一下CyclicBarrier的使用实例:

    复制代码
    public static class CyclicBarrierThread extends Thread
    {
        private CyclicBarrier cb;
        private int sleepSecond;
            
        public CyclicBarrierThread(CyclicBarrier cb, int sleepSecond)
        {
            this.cb = cb;
            this.sleepSecond = sleepSecond;
        }
            
        public void run()
        {
            try
            {
                System.out.println(this.getName() + "运行了");
                Thread.sleep(sleepSecond * 1000);
                System.out.println(this.getName() + "准备等待了, 时间为" + System.currentTimeMillis());
                cb.await();
                System.out.println(this.getName() + "结束等待了, 时间为" + System.currentTimeMillis());
            }
            catch (Exception e)
            {
                e.printStackTrace();
            } 
        }
    }
        
    public static void main(String[] args)
    {
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                System.out.println("CyclicBarrier的所有线程await()结束了,我运行了, 时间为" + System.currentTimeMillis());
            }
        };
        CyclicBarrier cb = new CyclicBarrier(3, runnable);
        CyclicBarrierThread cbt0 = new CyclicBarrierThread(cb, 3);
        CyclicBarrierThread cbt1 = new CyclicBarrierThread(cb, 6);
        CyclicBarrierThread cbt2 = new CyclicBarrierThread(cb, 9);
        cbt0.start();
        cbt1.start();
        cbt2.start();
    }
    复制代码

    看一下运行结果:

    复制代码
    Thread-0运行了
    Thread-2运行了
    Thread-1运行了
    Thread-0准备等待了, 时间为1444650316313
    Thread-1准备等待了, 时间为1444650319313
    Thread-2准备等待了, 时间为1444650322313
    CyclicBarrier的所有线程await()结束了,我运行了, 时间为1444650322313
    Thread-2结束等待了, 时间为1444650322313
    Thread-0结束等待了, 时间为1444650322313
    Thread-1结束等待了, 时间为1444650322313
    复制代码

    从运行结果看,由于是同一个CyclicBarrier,Thread-0先运行到了await()的地方,等着;Thread-2接着运行到了await()的地方,还等着;Thread-1最后运行到了await()的地方,所有的线程都运行到了await()的地方,所以三个线程以及指定的Runnable"同时"运行后面的代码,可以看到,await()之后,四个线程运行的时间一模一样,都是1444650322313。

    从使用来看,可能有人觉得CyclicBarrier和CountDownLatch有点像,都是多个线程等待相互完成之后,再执行后面的代码。实际上,CountDownLatch和CyclicBarrier都是用于多个线程间的协调的,它们二者的几个差别是:

    1、CountDownLatch是在多个线程都进行了latch.countDown()后才会触发事件,唤醒await()在latch上的线程,而执行countDown()的线程,执行完countDown()后会继续自己线程的工作;CyclicBarrier是一个栅栏,用于同步所有调用await()方法的线程,线程执行了await()方法之后并不会执行之后的代码,而只有当执行await()方法的线程数等于指定的parties之后,这些执行了await()方法的线程才会同时运行

    2、CountDownLatch不能循环使用,计数器减为0就减为0了,不能被重置;CyclicBarrier提供了reset()方法,支持循环使用

    3、CountDownLatch当调用countDown()方法的线程数等于指定的数量之后,可以唤起多条线程的任务;CyclicBarrier当执行await()方法的线程等于指定的数量之后,只能唤起一个BarrierAction

    注意,因为使用CyclicBarrier的线程都会阻塞在await方法上,所以在线程池中使用CyclicBarrier时要特别小心,如果线程池的线程过少,那么就会发生死锁了

    Callable、Future和FutureTask

    Callable

    Callable和Runnable差不多,两者都是为那些其实例可能被另一个线程执行的类而设计的,最主要的差别在于Runnable不会返回线程运算结果,Callable可以(假如线程需要返回运行结果)

    Future

    Future是一个接口表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。Future提供了get()、cancel()、isCancel()、isDone()四种方法,表示Future有三种功能:

    1、判断任务是否完成

    2、中断任务

    3、获取任务执行结果

    FutureTask

    FutureTask是Future的实现类,它提供了对Future的基本实现。可使用FutureTask包装Callable或Runnable对象,因为FutureTask实现了Runnable,所以也可以将FutureTask提交给Executor。

    使用方法

    Callable、Future、FutureTask一般都是和线程池配合使用的,因为线程池ThreadPoolExecutor的父类AbstractExecutorService提供了三种submit方法:

    1、public Future<?> subit(Runnable task){...}

    2、public <T> Future<T> submit<Runnable task, T result>{...}

    3、public <T> Future<T> submit<Callable<T> task>{...}

    第2个用得不多,第1个和第3个比较有用

    Callable+Future使用示例

    复制代码
    public static class CallableThread implements Callable<String>
    {
        public String call() throws Exception
        {
            System.out.println("进入CallableThread的call()方法, 开始睡觉, 睡觉时间为" + System.currentTimeMillis());
            Thread.sleep(10000);
            return "123";
        }
    }
        
    public static void main(String[] args) throws Exception
    {
        ExecutorService es = Executors.newCachedThreadPool();
        CallableThread ct = new CallableThread();
        Future<String> f = es.submit(ct);
        es.shutdown();
            
        Thread.sleep(5000);
        System.out.println("主线程等待5秒, 当前时间为" + System.currentTimeMillis());
            
        String str = f.get();
        System.out.println("Future已拿到数据, str = " + str + ", 当前时间为" + System.currentTimeMillis());
    }
    复制代码

    运行结果为:

    进入CallableThread的call()方法, 开始睡觉, 睡觉时间为1444654421368
    主线程等待5秒, 当前时间为1444654426369
    Future已拿到数据, str = 123, 当前时间为1444654431369

    看到任意一个利用Callable接口submit上去的任务,只要有一个Future接受它,Future便可以在程序任何地点尝试去获取这条线程返回出去的数据,时间可以比对一下,正好10000ms,即10s

    Callable+FutureTask使用示例

    有兴趣的可以看下源码,其实使用Callable+Future的方式,es.submit(ct)方法返回的Future,底层实现new出来的是一个FutureTask。那么,我们看一下Callable+FutureTask的方式:

    复制代码
    public static class CallableThread implements Callable<String>
    {
        public String call() throws Exception
        {
            System.out.println("进入CallableThread的call()方法, 开始睡觉, 睡觉时间为" + System.currentTimeMillis());
            Thread.sleep(10000);
            return "123";
        }
    }
        
    public static void main(String[] args) throws Exception
    {
        ExecutorService es = Executors.newCachedThreadPool();
        CallableThread ct = new CallableThread();
        FutureTask<String> f = new FutureTask<String>(ct);
        es.submit(f);
        es.shutdown();
            
        Thread.sleep(5000);
        System.out.println("主线程等待5秒, 当前时间为" + System.currentTimeMillis());
            
        String str = f.get();
        System.out.println("Future已拿到数据, str = " + str + ", 当前时间为" + System.currentTimeMillis());
    }
    复制代码

    看下运行结果:

    进入CallableThread的call()方法, 开始睡觉, 睡觉时间为1444655049199
    主线程等待5秒, 当前时间为1444655054200
    Future已拿到数据, str = 123, 当前时间为1444655059200

    和上面的写法运行结果一样,就不解释了

    使用Callable、Future和FutureTask的好处

    上面演示了两个例子,其实反映的是现实中一种情况,把上面的例子稍微扩展一下就是:

    有一个method()方法,方法中执行方法A返回一个数据要10秒钟,A方法后面的代码一共要执行20秒钟,但是这20秒的代码中有10秒的方法并不依赖方法A的执行结果,有10秒钟的代码依赖方法A的执行结果。此时若采用同步的方式,那么势必要先等待10秒钟,等待方法A执行完毕,返回数据,再执行后面20秒的代码。

    不得不说这是一种低效率的做法。有了Callable、Future和FutureTask,那么:

    1、先把A方法的内容放到Callable实现类的call()方法中

    2、method()方法中,Callable实现类传入Executor的submit方法中

    3、执行后面方法中10秒不依赖方法A运行结果的代码

    4、获取方法A的运行结果,执行后面方法中10秒依赖方法A运行结果的代码

  • 相关阅读:
    Druid 使用 Kafka 将数据载入到 Kafka
    Druid 使用 Kafka 数据加载教程——下载和启动 Kafka
    Druid 集群方式部署 —— 启动服务
    Druid 集群方式部署 —— 端口调整
    Druid 集群方式部署 —— 配置调整
    Druid 集群方式部署 —— 配置 Zookeeper 连接
    Druid 集群方式部署 —— 元数据和深度存储
    Druid 集群方式部署 —— 从独立服务器部署上合并到集群的硬件配置
    Druid 集群方式部署 —— 选择硬件
    Druid 独立服务器方式部署文档
  • 原文地址:https://www.cnblogs.com/sxw123/p/13840776.html
Copyright © 2011-2022 走看看