本文版权归作者所有,转载请标明出处。未与作者联系不得用于任何商业用途
2011年5月18日
关新全
山东东营 石油大学
2.2 同步
线程同步是用来调节线程之间的执行进度,在很多情况下,需要在某一点处,所有线程等待一次汇集,统计临时结果。或者主线程等待所有子线程执行结束,统计最终结果等。(这里的同步概念与经典的同步略有不同,这里仅强调协调线程的执行进度,与锁相关的内容不放入此处同步概念)。
Java提供了阻塞队列、CountDownLatch和CyclicBarrier三种方式的同步策略。阻塞队列使用了生产者消费者模式完成同步,CountDownLatch和CyclicBarrier是java专门设计的同步器。
2.2.1 阻塞队列
BlockingQueue比较适用于生产者消费者模式,生产者线程向队列中添加元素,消费者从队列中提取元素。在生产者向队列中添加元素时(put)如果队列已经满了,那么生产者线程需要阻塞式等待,直到有消费者线程从队列中提取了元素后,队列状态变为不满,生产者线程才可能被唤醒。同理,当消费者线程从队列中取元素(take)时,如果队列为空,那么消费者需要阻塞等待,直至有生产者线程向队列中添加元素,此时队列状态为非空时,消费者线程才可能被唤醒。
阻塞队列就是用来调节多个线程之间的执行进度的,使用阻塞队列可以完成线程的同步。
Demo2-6给出了使用阻塞队列完成线程同步的例子。主线程开启5个子线程,然后等待所有子线程执行结束(通过消耗阻塞队列中的元素)后,主线程输出自身的线程名,每个子进程都输出自己的线程名,同时向阻塞队列中生产一个元素。如果主线程调用take方法时,队列中没有元素,那么主线程会阻塞等待,直到其中的一个子线程向队列中添加元素为止。注意Demo2-6与Demo2-1的区别在于Demo2-6每次都是最后输出主线程。
阻塞队列还有很多复杂的方法,可以查看相应的官方文档,做更多的了解,而且Demo2-6只使用了阻塞队列中的一个实现(ArrayBlockingQueue),阻塞队列还有LinkedBlockingQueue、PriorityBlockingQueue等实现,可以从官方文档获取相关信息。
Demo2-6 阻塞队列完成线程同步
package com.upc.upcgrid.guan.advancedJava.chapter02;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueSync {
public static final int MAX_THREAD_SIZE = 5;
public static void main(String[] args) throws InterruptedException {
final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(MAX_THREAD_SIZE);
for(int i = 0 ; i < MAX_THREAD_SIZE ; i++)
{
new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName());
try {
queue.put(1);
} catch (InterruptedException e) {}
};
}.start();
}
for(int i = 0 ; i < MAX_THREAD_SIZE ; i++)
queue.take();
System.out.println(Thread.currentThread().getName());
}
}
Demo2-6 执行结果:
Thread-0
Thread-2
Thread-1
Thread-3
Thread-4
main
2.2.2 CountDownLatch
倒计时门栓,可以用来处理主线程等待子线程这种类型的同步。CountDownLatch中有一个计数器,创建CountDownLatch实例时需要指定计数器的值,主线程调用await方法等待计数器的值为0,每次调用countDown函数,计数器的值减一,直至计数器的值减少为0,主线程将会被唤醒。
Demo2-7给出了一个使用CountDownLatch完成同步的例子,这个与Demo2-6是等效的。在这个例子主线程先创建一个CountDownLatch的实例,并指定计数器的值为5,主线程调用await方法阻塞式等待latch实例计数器值减少为0。每个子线程在执行结束后都会调用countDown方法,将计数器的值减1。当所有子线程都执行结束,主线程将被唤醒。
Demo2-7 使用CountDownLatch完成同步
package com.upc.upcgrid.guan.advancedJava.chapter02;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchSync {
public static final int MAX_THREAD_SIZE = 5;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(MAX_THREAD_SIZE);
for(int i = 0 ; i < MAX_THREAD_SIZE ; i++)
{
new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName());
latch.countDown();
};
}.start();
}
latch.await();
System.out.println(Thread.currentThread().getName());
}
}
2.2.3 CyclicBarrier
CyclicBarrier与CountDownLatch相似,其内部也是维护一个计数器,不过与CountDownLatch不同的是,CyclicBarrier在子线程中调用await方法,并且调用await方法后,计数器的值减少1的同时,子线程被阻塞,直到指定数目的线程都调用了await方法后,所有的被CyclicBarrier阻塞的线程将都被唤醒。
Demo2-8给出了使用CyclicBarrier实现从0加到99的示例。在Demo2-8中,每个子线程都将自己计算的临时结果放入临时数组中,当所有子线程计算结束后,CyclicBarrier会开启一个新的线程计算汇总结果。在创建CyclicBarrier的实例时可以传入一个Runnable接口,当指定数目的线程到达await后,CyclicBarrier就会开启一个新线程执行传入的Runnable实例,当开启的Runnable实例执行结束后,每个线程又会从自己阻塞的地方继续执行未执行完的代码逻辑。在Demo2-8中,所有线程执行完计算任务后,CyclicBarrier开启一个新的线程计算统计结果,新线程结束后,主线程将结果输出,其他子线程继续执行await后面的未完成的输出线程名字的任务。
Demo2-8 计算从0-99的和,使用CyclicBarrier完成同步
package com.upc.upcgrid.guan.advancedJava.chapter02;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class OnceComputSync implements Runnable{
private int index;//下标
private int width;//计算加和数的个数
private CyclicBarrier bar;//障栅
private int[] sumArray;//临时和数组
public OnceComputSync(int index ,int width, CyclicBarrier bar,int[] sumArray) {
this.index = index;
this.width = width;
this.bar = bar;
this.sumArray = sumArray;
}
@Override
public void run() {
int end = (index + 1)*width;
int sum = 0;
for(int i = index * width ;i < end ; i++)
sum += i;
sumArray[index] = sum;
try {
bar.await();//阻塞等待
} catch (Exception e) {
}
//被唤醒后执行输出语句
System.err.println(Thread.currentThread().getName());
}
}
public class CyclicBarrierSync {
public static final int MAX_THREAD_SIZE = 10;
public static void main(String[] args) throws InterruptedException, ExecutionException {
final int[] sumArray = new int[MAX_THREAD_SIZE];
FutureTask<Integer> task;
CyclicBarrier bar = new CyclicBarrier(MAX_THREAD_SIZE,task = new FutureTask<Integer>( new Callable<Integer>() {
@Override
public Integer call() throws Exception {//统计所有线程的临时和
int sum = 0;
for(int i = 0 ;i < MAX_THREAD_SIZE; i++)
sum += sumArray[i];
return sum;
}
}));
for(int i = 0 ;i < MAX_THREAD_SIZE ; i++)//开启计算子线程
{
new Thread(new OnceComputSync(i,10, bar,sumArray)).start();
}
System.err.println(task.get());//调用get方法会阻塞式等待计算结果
}
}
Demo2-8执行结果:
4950
Thread-2
Thread-5
Thread-4
Thread-3
Thread-1
Thread-0
Thread-9
Thread-8
Thread-7
Thread-6