【Java并发】 - Cyclicbarrier使用以及原理

CyclicBarrier概述

  CyclicBarrier顾名思义是一个循环屏障,它允许一个或者多个线程之间互相等待,直到所有的线程都到达某个点这个时候屏障打开。而且这个屏障可以循环的使用。

CyclicBarrier使用

  假设有五个学生要考试,要求需要等到所有的学生都来齐了之后由老师宣布考试开始然后学生才能开始作答。
public class CyclicBarrierDemo {

	private static final int SHEET_NUM = 5;
	
	public static void main(String[] args) throws InterruptedException {
		CyclicBarrier barrier = new CyclicBarrier(SHEET_NUM, new Runnable() {
			@Override
			public void run() {
				System.out.println("Teacher annouce : Test start!");
			}
		});
		
		List threads = new ArrayList<>();
		
		for (int i = 0; i < SHEET_NUM; i ++) {
			Thread t = new Thread(new Lesson(barrier), "student" + i);
			threads.add(t);
			t.start();
		}
		
		for(Thread t : threads) {
			t.join();
		}
	}
}

class Lesson implements Runnable {
	
	private CyclicBarrier barrier = null;
	
	Lesson(CyclicBarrier barrier) {
		this.barrier = barrier;
	}
	
	@Override
	public void run() {
		
		long s = new Random().nextInt(1000);
		
		try {
			Thread.sleep(s);
			
			System.out.println(Thread.currentThread().getName() + " came after " + s + " millis");
			
			barrier.await();
			
			System.out.println(Thread.currentThread().getName() + " do the test");
			
		} catch (InterruptedException e) {
			//e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
	}
}


这里有五个代表学生的线程,同时在构造CyclicBarrier的时候传入了一个Runnable任务,相当于老师的线程。
运行结果:
student0 came after 25 millis
student1 came after 102 millis
student3 came after 561 millis
student4 came after 646 millis
student2 came after 928 millis
Teacher annouce : Test start!
student2 do the test
student3 do the test
student4 do the test
student0 do the test
student1 do the test

假设这个时候需求变了:等所有的学生都做完了之后老师还要宣布考试结束:
public class CyclicBarrierDemo {

	private static final int SHEET_NUM = 5;
	
	private static volatile boolean isStart = true;
	
	public static void main(String[] args) throws InterruptedException {
		CyclicBarrier barrier = new CyclicBarrier(SHEET_NUM, new Runnable() {
			@Override
			public void run() {
				if (isStart){
					isStart = false;
					System.out.println("Teacher annouce : Test start!");
				}
				else
					System.out.println("Teacher annouce : Test is over!!");
			}
		});
		
		List threads = new ArrayList<>();
		
		for (int i = 0; i < SHEET_NUM; i ++) {
			Thread t = new Thread(new Lesson(barrier), "student" + i);
			threads.add(t);
			t.start();
		}
		
		for(Thread t : threads) {
			t.join();
		}
	}
}

class Lesson implements Runnable {
	
	private CyclicBarrier barrier = null;
	
	Lesson(CyclicBarrier barrier) {
		this.barrier = barrier;
	}
	
	@Override
	public void run() {
		
		long s = new Random().nextInt(1000);
		
		try {
			Thread.sleep(s);
			
			System.out.println(Thread.currentThread().getName() + " came after " + s + " millis");
			
			barrier.await();
			
			Thread.sleep(s);
			
			System.out.println(Thread.currentThread().getName() + " has done the test");
		
			barrier.await();
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
	}
}

这里使用了一个布尔的变量判断老师的行为。 同时继续使用之前的barrier。
运行结果:

student4 came after 66 millis
student1 came after 201 millis
student2 came after 365 millis
student0 came after 668 millis
student3 came after 741 millis
Teacher annouce : Test start!
student4 has done the test
student1 has done the test
student2 has done the test
student0 has done the test
student3 has done the test
Teacher annouce : Test is over!!

在这个例子中可以看到CyclicBarrier的循环使用的功能,同时假设需求变为考完这场之后还要考第二场第三场的话同样只需要加上循环即可。
当然如果发生了异常的话CyclcBarrier也提供了一个reset()方法来重置栅栏。


源码

Generation内部类

private static class Generation {
    boolean broken = false;
}
Generation内部类中只有一个属性,这个布尔值的属性用来代表当前的栅栏是否处于被打破的状态。这个Generation故意用内部类的形式而不是直接使用一个布尔变量的原因是因为在循环使用的时候可以简单的new一个新的Generation的实例即可。

属性

//重入锁提供同步控制
private final ReentrantLock lock = new ReentrantLock();
//condition
private final Condition trip = lock.newCondition();
//需要等待通过栅栏的线程数
private final int parties;
//在所有线程都到达栅栏的时候的可选操作
private final Runnable barrierCommand;
//是否broken的状态控制generation
private Generation generation = new Generation();

//栅栏上还需要等待的线程个数(还没有达到栅栏的线程数量)
private int count;


CyclicBarrier通过count属性来控制等待的线程同时使用Generation的实例来记录栅栏状态。最后由重入锁提供同步的支持。


构造方法

public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

public CyclicBarrier(int parties) {
    this(parties, null);
}

初始化的时候count是等于parties的,所以每当一个线程抵达栅栏就对count做减1的操作直到count等于0时则代表全部需要等待的线程都已经抵达。

重要的方法


await

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

await方法实际上是直接调用的doawait方法,第一个参数代表是否是超时等待,第二个参数代表等待的时间

doawait


private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
	//同步
    lock.lock();
    try {
        final Generation g = generation;
		//如果栅栏的状态是broken的那么抛出异常
        if (g.broken)
            throw new BrokenBarrierException();

		//如果线程的中断标志为true那么打破栅栏并抛出中断异常
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
		//将count减1
        int index = --count;
		//如果count==0代表当前线程是等待的最后的一个线程
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
				//如果构造函数中传入了打破栅栏时的行为则执行
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
				//将栅栏的状态更新
                nextGeneration();
                return 0;
            } finally {
				//保证打破栅栏
                if (!ranAction)
                    breakBarrier();
            }
        }

		//自旋
		//直到所有的线程都抵达或者栅栏broken或者线程中断或者超时
        for (;;) {
            try {
				//根据是否是超时等待去挂起线程
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
				//如果g.broken为true则打破栅栏并抛出中断异常
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
					//恢复中断状态
					//即使这里没有中断那么接下来也即将结束等待,因此这次的中断被认为是接下来才会执行的行为
                    Thread.currentThread().interrupt();
                }
            }
			//g.broken标志为true则抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
			//如果此时generation被重置了(有可能是调用了reset)则返回未抵达的线程个数
            if (g != generation)
                return index;

			//超时则打破栅栏并抛出异常
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

JDk中的描述:
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。 
如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它,且在发生以下情况之一前,该线程将一直处于休眠状态: 
  • 最后一个线程到达;或者 
  • 其他某个线程中断当前线程;或者 
  • 其他某个线程中断另一个等待线程;或者 
  • 其他某个线程在等待 barrier 时超时;或者 
  • 其他某个线程在此 barrier 上调用 reset()。 
如果当前线程: 

在进入此方法时已经设置了该线程的中断状态;或者 
在等待时被中断 
则抛出 InterruptedException,并且清除当前线程的已中断状态。 
如果在线程处于等待状态时 barrier 被 reset(),或者在调用 await 时 barrier 被损坏,抑或任意一个线程正处于等待状态,则抛出 BrokenBarrierException 异常。 


如果任何线程在等待时被 中断,则其他所有等待线程都将抛出 BrokenBarrierException 异常,并将 barrier 置于损坏状态。 


如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作,则在允许其他线程继续运行之前,当前线程将运行该操作。如果在执行屏障操作过程中发生异常,则该异常将传播到当前线程中,并将 barrier 置于损坏状态。 


breakbarrier

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

调用breakBarrier的时候会唤醒所有在condition上等待的线程

reset

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}
调用reset的时候会先打破栅栏然后再重置generation


你可能感兴趣的:(Java并发)