`
youyu4
  • 浏览: 435302 次
社区版块
存档分类
最新评论

Java多线程之常用类--闭包、栅栏、信号量、FutureTask

 
阅读更多

Java多线程之常用类--闭包、栅栏、信号量、FutureTask

 

 

闭锁

 

用途:可用于命令一组线程在同一个时刻开始执行某个任务,或者等待一组相关的操作结束。尤其适合计算并发执行某个任务的耗时。

 

public class CountDownLatchTest {  
  
    public void timeTasks(int nThreads, final Runnable task) throws InterruptedException{  
        final CountDownLatch startGate = new CountDownLatch(1);  
        final CountDownLatch endGate = new CountDownLatch(nThreads);  
          
        for(int i = 0; i < nThreads; i++){  
            Thread t = new Thread(){  
                public void run(){  
                    try{  
                        startGate.await();  
                        try{  
                            task.run();  
                        }finally{  
                            endGate.countDown();  
                        }  
                    }catch(InterruptedException ignored){  
                          
                    }  
                      
                }  
            };  
            t.start();  
        }  
          
        long start = System.nanoTime();  
        System.out.println("打开闭锁");  
        startGate.countDown();  
        endGate.await();  
        long end = System.nanoTime();  
        System.out.println("闭锁退出,共耗时" + (end-start));  
    }  
      
    public static void main(String[] args) throws InterruptedException{  
        CountDownLatchTest test = new CountDownLatchTest();  
        test.timeTasks(5, test.new RunnableTask());  
    }  
      
    class RunnableTask implements Runnable{  
  
        @Override  
        public void run() {  
            System.out.println("当前线程为:" + Thread.currentThread().getName());  
              
        }     
    } 

 

执行结果为:  
打开闭锁  
当前线程为:Thread-0  
当前线程为:Thread-3  
当前线程为:Thread-2  
当前线程为:Thread-4  
当前线程为:Thread-1  
闭锁退出,共耗时1109195  

 

 

 

栅栏

 

用途:用于阻塞一组线程直到某个事件发生。所有线程必须同时到达栅栏位置才能继续执行下一步操作,且能够被重置以达到重复利用。而闭锁式一次性对象,一旦进入终止状态,就不能被重置。

 

  • 闭锁用于等待事件
  • 栅栏用于等待线程
public class CyclicBarrierTest {  
    private final CyclicBarrier barrier;  
    private final Worker[] workers;  
  
    public CyclicBarrierTest(){  
        int count = Runtime.getRuntime().availableProcessors();  
        this.barrier = new CyclicBarrier(count,  
                new Runnable(){  
  
                    @Override  
                    public void run() {  
                        System.out.println("所有线程均到达栅栏位置,开始下一轮计算");  
                    }  
              
        });  
        this.workers = new Worker[count];  
        for(int i = 0; i< count;i++){  
            workers[i] = new Worker(i);  
        }  
    }  
    private class Worker implements Runnable{  
        int i;  
          
        public Worker(int i){  
            this.i = i;  
        }  
  
        @Override  
        public void run() {  
            for(int index = 1; index < 3;index++){  
                System.out.println("线程" + i + "第" + index + "次到达栅栏位置,等待其他线程到达");  
                try {  
                    //注意是await,而不是wait  
                    barrier.await();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                    return;  
                } catch (BrokenBarrierException e) {  
                    e.printStackTrace();  
                    return;  
                }  
            }  
        }  
          
    }  
      
    public void start(){  
        for(int i=0;i<workers.length;i++){  
            new Thread(workers[i]).start();  
        }  
    }  
      
    public static void main(String[] args){  
        new CyclicBarrierTest().start();  
    }  
} 

 

执行结果为:  
线程0第1次到达栅栏位置,等待其他线程到达  
线程1第1次到达栅栏位置,等待其他线程到达  
线程2第1次到达栅栏位置,等待其他线程到达  
线程3第1次到达栅栏位置,等待其他线程到达  
所有线程均到达栅栏位置,开始下一轮计算  
线程3第2次到达栅栏位置,等待其他线程到达  
线程2第2次到达栅栏位置,等待其他线程到达  
线程0第2次到达栅栏位置,等待其他线程到达  
线程1第2次到达栅栏位置,等待其他线程到达  
所有线程均到达栅栏位置,开始下一轮计算 

 

 

 

信号量

 

用途:用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量可以用来实现某种资源池,或者对容器施加边界。

 

public class SemaphoreTest<T> {  
    private final Set<T> set;  
      
    private final Semaphore sema;  
      
    public SemaphoreTest(int bound){  
        this.set = Collections.synchronizedSet(new HashSet<T>());  
        this.sema = new Semaphore(bound);  
    }  
      
    public boolean add(T o) throws InterruptedException{  
        sema.acquire();  
        boolean wasAdded = false;  
        try{  
            wasAdded = set.add(o);  
            return wasAdded;  
        }finally{  
            if(!wasAdded){  
                sema.release();  
            }  
        }  
    }  
      
    public boolean remove(T o){  
        boolean wasRemoved = set.remove(o);  
        if(wasRemoved){  
            sema.release();  
        }  
        return wasRemoved;  
    }  
      
    public static void main(String[] args) throws InterruptedException{  
        int permits = 5;  
        int elements = permits + 1;  
        SemaphoreTest<Integer> test = new SemaphoreTest<Integer>(permits);  
        for(int i = 0;i < elements; i++){  
            test.add(i);  
        }  
    }  
} 

 

输出结果:
由于实际待添加的元素个数大于信号量所允许的数量,因此最后一次添加时,会一直阻塞。

 

 

 

FutureTask

 

       当一个计算的代价比较高,譬如比较耗时,或者耗资源,为了避免重复计算带来的浪费,当第一次计算后,通常会将结果缓存起来。比较常见的方式就是使用synchronized进行同步,但该方式带来的代价是被同步的代码只能被串行执行,如果有多个线程在排队对待计算结果,那么针对最后一个线程的计算时间可能比没有使用缓存的时间会更长。

第二种方式是采用ConcurrentHashMap,但对于耗时比较长的计算过程来说,该方式也存在一个漏洞。如果在第一个线程正在计算的过程中,第二个线程开始获取结果,会发现缓存里没有缓存结果,因此第二个线程又启动了同样的计算,这样就导致重复计算,违背了缓存的初衷。计算过程越长,则出现这种重复计算的几率就会越大。

 

       通过第二种方式的缺点分析,得知真正要缓存的应该是计算是否已被启动,而不是等待漫长的计算过程结束后,再缓存结果。一旦从缓存中得知某个计算过程已被其他线程启动,则当前线程不需要再重新启动计算,只需要阻塞等待计算结果的返回。FutureTask就是实现该功能的最佳选择。

 

public class Memoizer<A, V> implements Computable<A, V> {  
    private ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();  
    private final Computable<A, V> c;  
  
    public Memoizer(Computable<A, V> c) {  
        this.c = c;  
    }  
  
    @Override  
    public V compute(final A a) {  
        while (true) {  
            Future<V> f = cache.get(a);  
            if (null == f) {  
                Callable<V> eval = new Callable<V>() {  
  
                    @Override  
                    public V call() throws Exception {  
                        return c.compute(a);  
                    }  
  
                };  
                FutureTask<V> ft = new FutureTask<V>(eval);  
                f = cache.putIfAbsent(a, ft);  
                if (null == f) {  
                    f = ft;  
                    ft.run();  
                }  
            }  
            try {  
                return f.get();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
                cache.remove(a, f);  
            } catch (ExecutionException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
  
    public static void main(String[] args) throws InterruptedException,  
            ExecutionException {  
        Computable<Integer, String> c = new Computable<Integer, String>() {  
  
            @Override  
            public String compute(Integer a) {  
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                String result = "由线程:" + Thread.currentThread().getName()  
                        + "计算得到" + a + "的结果";  
                return result;  
            }  
        };  
  
        Memoizer<Integer, String> memoizer = new Memoizer<Integer, String>(c);  
  
        ExecutorService executorService = Executors.newFixedThreadPool(3);  
  
        for (int i = 0; i < 3; i++) {  
            Task<Integer, String> task = new Task<Integer, String>(memoizer, 10);  
            String result = executorService.submit(task).get();  
            System.out.println(result);  
        }  
        executorService.shutdown();  
    }  
} 

 

结果如下:  
由线程:pool-1-thread-1计算得到10的结果  
由线程:pool-1-thread-1计算得到10的结果  
由线程:pool-1-thread-1计算得到10的结果  
当然如果仅仅只是采用FutureTask,仅仅只是减小了启动同一个计算过程的概率。当两个线程同时经过compute方法时,还是会出现重复启动同一个计算的情况,上面的例子通过结合ConcurrentHashMap 的putIfAbsent方法解决了这个问题  

 

分享到:
评论

相关推荐

    Java闭包 Java闭包

    闭包在现在的很多流行的语言中都存在,例如 C++、C# 。闭包允许我 们创建函数指针,并把它们作为参数传递。在这篇文章中,将粗略的看一遍Java8的特性,并介绍 Lambda表达式。而且将试着放一些样例程序来解释一些概念...

    极小S-负传递闭包的一个求解方法

    在给定的文件信息中,提到的研究内容涉及了模糊逻辑、模糊关系以及负传递闭包的相关知识点。以下是对文件中知识点的详细解释。 首先,标题中提到的“极小S-负传递闭包”的概念是模糊逻辑领域中的一个高级主题。在...

    swift菜鸟入门视频教程-07-闭包

    在这个“Swift菜鸟入门视频教程-07-闭包”中,我们将深入探讨Swift中的一个重要概念——闭包。闭包在编程中是一种功能强大且灵活的工具,它允许我们定义可存储和传递的匿名函数。 闭包在Swift中的基本概念: 1. **...

    论文研究-基于网络闭包理论的交易型社区网络演化研究.pdf

    本研究以国内最大的交易型网站(淘宝网)为平台,选取其中最活跃的圈子社区之一(共7902名会员,有效样本为6195名会员)为研究对象,分析交易型社区网络闭包机制相对于传统社交网络闭包机制的差异性.研究结果表明: (1)由于...

    python 03、PYTHon 模块包异常处理 4-1_闭包、装饰器_Day04_AM.mp4

    python 03、PYTHon 模块包异常处理 4-1_闭包、装饰器_Day04_AM.mp4

    ch8-关系的闭包和等价关系-集合论1

    在计算机科学中,集合论...总结来说,关系的闭包和等价关系在理论计算机科学中扮演着核心角色,它们在数据结构、图论、编译原理和算法设计等多个领域都有广泛应用。理解并掌握这些概念对于深入学习计算机科学至关重要。

    离散数学-关系的闭包.ppt

    其中,关系的闭包是一个核心概念,它涉及到集合论、二元关系和函数等多个领域。 首先,我们要理解集合论的基本概念。集合是由特定对象组成的整体,可以进行并集、交集、差集等基本运算。在有穷集合中,我们还可以...

    ios-Swift使用闭包实现类似于BlocksKit添加点击事件功能.zip

    在本案例中,我们关注的是"ios-Swift使用闭包实现类似于BlocksKit添加点击事件功能",它通过ClosuresKit库实现了类似的功能。 首先,我们要理解什么是闭包(Closure)。闭包是Swift中的一个重要概念,它可以捕获和...

    Golang mk教程-Go语言视频零基础入门到精通项目实战web编程

    第04天-基础-闭包、数组、切片、锁机制 第05天-进阶-排序、链表、二叉树、接口 第06天-进阶-接口与反射详解 第07天-进阶-接口实例、终端文件读写、异常处理 第08天-进阶-goroute详解、定时器与单元测试 第09天-高级-...

    论文研究-求解最小闭包球问题改进的SMO-型算法.pdf

    研究[n]维空间中[m]个点的最小闭包球(MEB)问题。通过结合确定并删除内部点的技术到序列最小最优化(SMO)方法中,提出一种近似求解MEB问题的改进的SMO-型算法。证明了该算法具有线性收敛性。数值结果表明对于一些...

    多线程精品资源--Python实用教程,包括:Python基础,Python高级特性,面向对象编程,多线程,数据库,.zip

    Python的`threading`库提供了线程创建、同步机制(如锁、信号量、事件)以及线程池等工具。然而,由于全局解释器锁(GIL)的存在,Python在CPU密集型任务上可能不如其他语言表现优秀,但仍然可以通过多线程处理多个I...

    17-闭包和装饰器(python和linux高级编程阶段 代码和截图)

    17-闭包和装饰器(python和linux高级编程阶段 代码和截图)17-闭包和装饰器(python和linux高级编程阶段 代码和截图)17-闭包和装饰器(python和linux高级编程阶段 代码和截图)17-闭包和装饰器(python和linux高级...

    js学习之----深入理解闭包

    JavaScript中的闭包是一种高级特性,它允许函数访问并操作其外部作用域的变量,即使在其外部函数已经执行完毕后。闭包是理解和掌握JavaScript高级编程的关键,因为它涉及到作用域、内存管理和函数的特性。 **闭包的...

    yjc930214#Blog-1#闭包1

    1.可以读取函数内部的变量 2.可以是变量的值长期保存在内存中,生命周期比较长 3.可以用来实现JS模块

    M- L-闭包系统间的特殊映射及其范畴性质 (2012年)

    定义了M- L-闭包系统和M- L-闭包系统间的连续映射、开映射、闭映射,讨论了这些映射的性质.证明了范畴M- L-CS (即M- L-闭包系统及它们之间的连续映射构成的范畴)是topological construct .作为应用,给出了M- L-闭包...

    极小S-负传递闭包的一个求解方法 (2012年)

    模糊数学是数学的一个分支,主要研究模糊集合理论以及与之相关的一系列数学结构。在模糊数学的研究中,模糊关系以及它们的性质扮演着重要的角色。模糊关系的传递性是其中的基本概念之一,它涉及关系的传递性质,即若...

    Java闭包练习

    Java闭包是一个重要的编程概念,尤其在Java 8及以后的版本中得到了广泛的应用。闭包是一种函数式编程特性,允许函数保留对外部环境的引用,即使该函数被作为独立实体传递或执行。在Java中,接口中的默认方法和Lambda...

    Swift3.0 闭包整理 - CocoaChina_让移动开发更简单1

    - **闭包表达式中的`self`**:在类的方法中定义闭包时,需要通过`self`关键字来访问实例的属性和方法。为了防止无限递归,可以使用`unowned self`或`weak self`来避免强引用循环。 - **闭包的嵌套**:一个闭包可以...

    17-18-Python闭包函数+生成器+装饰器及原理

    在Python的世界里,闭包、生成器和装饰器是三个非常重要的概念,它们对于编写高效、灵活的代码至关重要。 首先,我们来详细解释一下闭包。闭包是在Python中函数的一种特性,它是指有权访问另一个函数作用域中的变量...

    ios-闭包传值.zip

    例如,如果一个类持有一个强引用到闭包,而闭包内部又引用了该类的实例,那么两者都无法被释放,从而导致内存泄漏。为解决这个问题,Swift提供了弱引用(weak)和无主引用(unowned)来打破这种循环引用。 总结: ...

Global site tag (gtag.js) - Google Analytics