欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

(十)同步器

程序员文章站 2022-07-12 19:08:55
...

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。