`

(十)同步器

阅读更多

java.util.concurrent包中包含了几个能帮助人们互相合作的线程集的类。这些这些机制具有为线程之间的 共用结点模式(common rendezvos patterns)提供的“预置功能”。
如果有一个相互合作的线程集满足这些行为模式之一,那么应该直接重用合适的库类,而不要试图提供手工的锁与条件集合。
共有以下5中同步器
(1)CyclicBarrier                                            
作用 :  一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。即,允许线程集等待直至其中预定数目的线程,到达一个公共屏障点,然后选择可以执行一个处理屏障的动作。
使用场景 : 当大量的线程需要在它们的结果可用之前完成时    

(2)CountDownLatch            
作用 :  一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。即,允许线程集等待直到计数器减为0。
使用场景 : 当一个或多个线程需要等待直到指定数目的事件发生。

(3)Exchanger                        
作用 : 可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。即,允许两个线程在要交换的对象准备好时交换对象。
使用场景 : 当两个线程在同一个数据结构的两个实例上的时候,一个向实例添加数据,而另一个从实例清除数据。                                    

(4)Semaphore
作用 : 一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。即,允许线程集等待直到被允许继续运行为止。
使用场景 : 限制访问资源的线程总数。如果许可数是1,常常阻塞线程,直到另一个线程给出许可为止。

(5)SynchronousQueue        
作用 : 一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的头 是尝试添加到队列中的首个已排队插入线程的元素;如果没有这样的已排队线程,则没有可用于移除的元素并且poll() 将会返回 null。对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空 collection。此队列不允许 null 元素。即,允许一个线程把对象交给另一个线程。
在没有显式同步的情况下,当两个线程准备好将一个对象从一个线程传递到另一个时。                    

1.信号量
一个信号量管理许多的许可证(permits)。为了通过信号量,线程通过调用acquire请求许可。许可数是固定的,由此限制了通过的线程数量。其他线程可以调用release释放许可。
许可实际上是一个计数。许可不是必须由获取它的线程释放。任何线程都可以释放任意数目的许可。如果释放的许可多于可用的许可的最大数,信号量只是被设置为可用许可的最大数目。

2.倒计时门栓
一个倒计时门栓(CountDownLatch)让一个线程集等待直到计数变为0。倒计时门栓是一次性的。一点计数为0,就不能再重用了。
一个有用的特例是计数值为1的门栓,实现一个只能通过一次的门。线程在门外等候直到另一个线程将计数器值置为0。
e.g.    一个线程集需要一些初始的数据来完成工作。工作线程被启动并在门外等候。另一个线程准备数据。当数据准备好的时候,调用CountDown,所有工作器线程就可以继续运行了。
        然后,第二个门栓检查什么时候所有工作器线程完成工作。用线程初始化门栓。每个工作器线程在结束前将门栓计数减1。另一个获取工作结果的线程在门外等候,一旦所有工作器线程终止,该线程继续运行。
        
3.屏障/障栅
CyclicBarrier类实现了一个集结点(rendezvous)称为屏障/障栅(barrier)。考虑大量线程运行在一次计算的不同部分的情况,当所有部分都准备好时,需要把结果组合在一起。当一个线程完成了它那部分任务之后,我们让它运行到屏障/障栅处。一旦所有的线程都到达了这个屏障/障栅,屏障/障栅就撤销,线程就可以继续运行。
(1)构造一个屏障/障栅,并给出参与的线程数
    CyclicBarrier barrier = new CyclicBarrier(nThreads);
(2)每一个线程做一些工作,完成后在屏障/障栅上调用await
    public void run(){
        doWork();
        barrier.await();
        ...
    }
(3)如果任何一个在屏障/障栅上等待的线程离开了屏障/障栅,那么屏障/障栅就破坏了(线程离开的原因:线程调用的await超时或因为它被中断了),在这种情况下所有其他线程的await方法抛出BrokenBarrierException异常。那些已经在等待的线程立即终止await的调用。
(4)可以提供一个可选的屏障/障栅动作(barrier action),当所有线程到达屏障/障栅的时候就会执行这一动作,该动作可以收集那些单个线程的运行结果。
    Runnable barrierAction = ...;
    CyclicBarrier barrier = new CyclicBarrier(nThreads, barrierAction);

注意 : 屏障/障栅被称为是循环的(cyclic),因为可以在所有等待线程被释放后重用,而CountDownLatch只能被使用一次。

4.交换器
当两个线程在同一个数据缓冲区的两个实例上工作的时候,就可以使用交换器(Exchanger)。
典型的情况是,一个线程向一个缓冲区填入数据,另一个线程消耗这些数据,当它们都完成后,相互交换缓冲区。

5.同步队列
同步队列是一种将生产者与消费者配对的机制。当一个线程调用SynchronousQueue的put方法时,它会阻塞直到另一个线程调用task方法为止,反之亦然。与Exchanger的情况不同,数据仅仅沿一个方向传递,从生产者到消费者。
即使SynchronousQueue类实现了BlockingQueue接口,从概念上讲,它依然不是一个队列。它没有包含任何元素,它的size方法总是返回0。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics