多线程系列提高(7)--同步工具类

来源:互联网 发布:mac firefly安装 编辑:程序博客网 时间:2024/06/06 05:01

同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列可以作为同步工具类,其它类型的同步工具类还包括信号量(Semaphore)、栏栅(Barrier)、以及闭锁(Latch)。
所有的同步工具类都包含一些特定的结构化属性:它们封装了一些状态,这些状态将决定执行同步工具类的线程是继续执行还是等待,此外还提供了一些对状态进行操作,以及另一些方法用于高效的等待同步工具类进入到预期状态。

一、闭锁(Latch)

闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门会一直关闭的,并且没有任何线程课通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动直到其它活动都完成时才继续执行。比如等待直到某个操作的所有参与者(例如,在多玩家游戏中的所有玩家)都就绪再继续执行,在这种情况下,当所有玩家都准备就绪时,闭锁到达结束状态。

CountDownLatch是一种灵活的闭锁实现,可以使一个或多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。countDown方法递减计数器,表示有一个事件已经发生了,而await方法等待计数器达到0,这表示所有需要等待的事件都已经发生。如果计数器的值非0,那么await会一直阻塞直到计数器为0,或者等待中的线程中断,或者等待超时。
代码示例:
TestHarness中给出了闭锁的两种用法。TestHarness创建一定数量的线程,利用它们并发的执行指定的任务。它使用两个闭锁,分别表示“起始门”和“结束门”。起始门计数器的初始值为1,而结束门计数器的初始值为工作线程的数量。每个工作线程首先要做的值就是在启动门上等待,从而确保所有线程都就绪后才开始执行,而每个线程要做的就是最后一件事情是将调用结束门的countDown方法减1,这能使主线程高效的等待直到所有工作线程都执行完成,因此可以统计所消耗的时间。

//在计时测试中使用CountDownLatch来启动和停止线程public class TestHarness{    public long timeTasks(int nThreads,final Runnable task){    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();    startGate.countDown();    endGate.await();    long end=System.nanoTime();    return end-start;    }}

二、信号量(Semaphore)

信号量(Semaphore)用来控制同时访问某个特定资源的操作数量,或者执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。
Semaphore中管理着一组虚拟的许可(permit),许可的初始数量可通过构造函数来指定。在执行操作时可以首先获得许可,并在使用以后释放许可。如果没有许可,那么acquire将阻塞直到由许可(或者直到被中断或者操作超时)。release方法将返回一个许可信号量。计算信号量的一种简化形式是二值信号量,即初始值为1的Semaphore,二值信号量可以用作互斥体,并具备不可重入的加锁语义,并具备不可重入的加速语义:谁拥有这个唯一的许可,谁就拥有了互斥锁。
Semaphore可以用于实现资源池,例如数据库连接池。我们可以构造一个固定长度的资源池,当池为空时,请求资源将会失败,但你真正希望看到的行为是阻塞而不是失败,并且当池非空时接触阻塞。如果将Semaphore的计数值初始化为池的大小,并在从池中获取一个资源之前首先调用acquire方法获取一个许可,在将资源返回给池之后调用release释放许可,那么acquire将一直阻塞直到资源池不为空。
同样可以使用Semaphore将任何一种容器变成有界阻塞容器,如下面程序,信号量的计数值会初始化为容器容量的最大值。add操作在向底层容器中添加一个元素之前,首先要获得一个许可。如果add操作没有添加任何元素,那么会立刻释放许可。同样,remove操作释放一个许可,使更多的元素能否添加到容器中。底层的Set实现并不知道边界的任何信息,这是由BoundedHashSet来处理的。

//使用Semaphore为容器设置边界public class BoundedHashSet<T>{    private final Set<T> set;    private final Semaphore sem;    public BoundedHashSet(int bound){        this.set=Collections.synchronizedSet(new HashSet<T>());        sem=new Semaphore(bound);    }    public boolean add(T o) throws InterruptedException{    sem.acquire();    boolean wasAdded=false;    try{        wasAdded=set.add(o);       return wasAdded;    }    finally {        if(!wasAdded)        sem.release();    }    }    public boolean remove(Object 0){        boolean wasRemoved=set.remove(o);        if(wasRemoved)            sem.release();            return wasRemoved;    }}

三、栏栅(Barrier)

栏栅(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生。栏栅与闭锁的关键区别在于,所有的线程必须同时到达栏栅位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。栅栏用于实现一些协议,例如几个家庭决定在某个地方集合:“所有人6::00在麦当劳碰头,到了以后要等其他人,之后再讨论下一步要做的事情”
CyclicBarrier可以使一定数量的参与方反复的在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置,如果所有线程都到了栅栏位置,那么栅栏将打开,此时所有线程都被释放,而栅栏将被重置以便下次使用。如果对await的调用超时,或者await阻塞的线程被中断,那么栅栏就被认为是打破了,所有阻塞的await调用都将终止并抛出BrokenBarrierException。如果成功的通过栅栏,那么await将为每个线程返回一个唯一的到达的索引号,我们可以利用这些索引来选取产生一个领导线程,并在下一次迭代中由该领导线程执行一些特殊的工作。CyclicBarrier还可以使你将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏时会执行它,但在阻塞线程被释放之前是不能执行的。

0 0