所谓的同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列,信号量,栅栏,以及闭锁均可以作为同步工具类来使用,他们封闭了一些状态,这些状态将决定执行同步工具类的线程是继续执行还是等待,此外还提供了一些方法对状态进行操作,以及另外一些方法用于高效的等待同步工具类进入预期状态。
阻塞队列:
BlockingQueue接口定义了可阻塞的put和take方法,同时也提供了非阻塞式的offer和poll方法。如果队列已满,那么put方法将阻塞直到有空间可用,如果队列为空那么take方法将会阻塞直到队列中有元素可以使用。而offer以及poll两个方法当队列已满或者队列为空而存储失败的时候,返回false。因此阻塞队列是利用队列的存储状态来控制线程的执行状态。
可以利用阻塞队列使用生产者——消费者模式,生产者把数据放入队列,而消费者从队列中读取数据,生产者不需要知道消费者的状态和数量,消费者同样不需要知道生产者是谁,只是从队列中读取数据进行使用。
/**
*生产者
*/
public class CreateTask implements Runnable
{
private BlockingQueue<Integer> queue;
public CreateTask(BlockingQueue<Integer> queue)
{
this.queue = queue;
}
public void run()
{
try
{
while(true)
{
queue.put(new Random().nextInt());
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
}
}
/**
*消费者
*/
public class ResumnTask implements Runnable
{
private BlockingQueue<Integer> queue;
public ResumnTask(BlockingQueue<Integer> queue)
{
this.queue = queue;
}
public void run()
{
try
{
while(true)
{
System.out.println(queue.take());
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
}
}
public class Main
{
public static void main(String[] args)
{
BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
new Thread(new CreateTask(queue)).start();
new Thread(new ResumnTask(queue)).start();
}
}
生产者生产Integer数据并放入队列中,消费者从队列中读取数据打印
闭锁
闭锁可以延迟线程的进度直到其到达终止状态,闭锁相当于一扇门,初始状态为关闭的,没有任何线程可以通过,当满足一定的条件之后,闭锁这扇门会打开,允许所有的线程通过。闭锁可以确保某些任务知道其他的任务都完成之后才继续执行。
CountDownLatch是闭锁的一种实现,其状态包括一个计数器,该计数器在创建闭锁的时候被初始化为一个正整数,表示需要等待的事件数量。countDown方法用来改变闭锁的状态,调用一个闭锁的计数器就会递减,await方法会一直阻塞直到计数器变为0才开始执行。
public class LatchTest
{
public static void main(String[] args) throws InterruptedException
{
Runnable task = new Runnable()
{
@Override
public void run()
{
int i = 0;
while(i < 1000000){
i++;
}
}
};
new LatchTest().timeTasks(10, task);
}
//方法中有两个闭锁,一个闭锁用于等待所有的线程生成完毕,然后才执行线程内的计算任务,另外一个闭锁直到所有的线程计算完毕之后才计算所有线程计算完成的用时
public void timeTasks(int nThreads, final Runnable task) throws InterruptedException
{
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
//产生指定量的线程,每个线程完成计算任务之后调用endGate的countDown方法,直到生产的所有线程执行完毕之后endGate.await();才会停止阻塞
for(int i = 0; i < nThreads; i++){
Thread t = new Thread(){
public void run(){
try
{
startGate.await(); //阻塞线程,知道调用闭锁startGate的countDown方法
try{
task.run();
}finally{
endGate.countDown(); //endGate这个闭锁的计数器减1
}
} catch (Exception e) {
// TODO: handle exception
}
}
};
t.start();
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
System.out.println(end - start);
}
}
信号量
计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量,计数信号量还可以用来实现某种资源池或者对容器施加边界。
Semaphore中管理者一组虚拟的许可,通过构造函数来进行初始化。在执行操作的时候可以首先获得许可(r如果还有剩余的许可),并在使用后释放许可,如果没有许可那么acquire方法将阻塞直到有许可。release方法将返回一个许可信号量。因此我们可用使用Semaphore将任何容器变成有界的阻塞容器。
/**
* 通过Semaphore将HashSet编程有界阻塞容器
* @param <T>
*/
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 t) throws InterruptedException{
sem.acquire();
boolean added = false;
try{
added = set.add(t);
return added;
}finally{
//如果没有添加,那么释放获取到的许可
if(!added){
sem.release();
}
}
}
public boolean remove(T t){
boolean removed = set.remove(t);
if(removed){
//删除成功的时候释放许可
sem.release();
}
return removed;
}
}
栅栏
栅栏和闭锁有点像,闭锁是一种一次性的对象,一旦打开,就不能被重置。栅栏会阻塞一组线程知道某个事件发生,可以反复使用
CylicBarrier可以使一定数量的参与方反复的在栅栏位置汇集,当线程到达栅栏的位置时将调用await方法,这个方法会阻塞直到所有的线程都到达栅栏的位置,如果所有的线程均到达了栅栏的位置,那么栅栏将打开,所有的线程都被释放,而栅栏将被重置以便下次使用,如果成功的通过栅栏,那么await方法将为每个线程返回一个唯一的到达索引号,CyclicBarrier还可以传递一个栅栏操作给其构造函数,这是一个Runnable,当成功通过栅栏时会执行它。
public class CyclicBarrierTest
{
private final CyclicBarrier barrier;
private Worker[] workers;
public static void main(String[] args) throws InterruptedException
{
CyclicBarrierTest barrier = new CyclicBarrierTest(10);
Worker[] works = barrier.getWorkers();
// 生成的works.length -1个线程会在执行到int num = barrier.await();的时候被阻塞,等待还没有到达栅栏的线程
for(int i = 0; i < works.length -1; i++){
new Thread(works[i]).start();
}
Thread.currentThread().sleep(5000);
//最后一个线程到达栅栏,所有的线程继续执行int num = barrier.await();之后的代码
new Thread(works[works.length-1]).start();
}
public CyclicBarrierTest(int count){
barrier = new CyclicBarrier(count);
workers = new Worker[10];
for(int i = 0; i < count; i++){
workers[i] = new Worker();
}
}
private class Worker implements Runnable
{
@Override
public void run()
{
//dosomething
try{
int num = barrier.await();
}catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getId());
}
}
public Worker[] getWorkers()
{
return workers;
}
public void setWorkers(Worker[] workers)
{
this.workers = workers;
}
}