首先,锁也是一种Synchronizer ,包括ReentrantLock, synchronized,是为了确保共享数据被完整一致的修改,不被破坏。
其他的,比如Object.wait(), notify()等 主要用于合作的线程之间彼此协调,Blocking Queue,FurtureTask 都是synchronizer
CountDownLatch
有两个方法await() 和countDown(),用于主线程调用latch.await(),每个子线程完整自己的工作后调用latch.countDown(),当所有的子线程都表示down了,即latch的计数为0了, 主线程才从await返回往下执行。 类似Thread.join(),但是不需要针对Thread对象本身操作,而且,子线程done了不一定结束,调了latch.countDown()后还可以继续干别的事。
初始计数值的大小,对应子任务的数目,而不一定是子线程的数目,可以一个子线程对应一个task,也可以多个
CyclicBarrier 栅栏
CyclicBarrier只有一个方法await(),好比一伙人到一个地方活动,先到的人先await(), 等所有的人都到了,才开始下一步活动。有几个人初始值就设为几,当所有的线程都到达barrier.await()这个点,大家都从await()中唤醒往下执行,最后一个调barrier.await的线程其实不阻塞。CyclicBarrier的构造函数还可以指定一个Runable,表示大家都到齐之后可以执行一个活动,这个是CyclicBarrier内部新开一个线程去执行的。
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCyclicBarrier {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Random random=new Random();
final CyclicBarrier barrier=new CyclicBarrier(4,new Runnable(){
@Override
public void run() {
System.out.println("大家都到齐了,开始happy去");
}});
for(int i=0;i<4;i++){
exec.execute(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"到了,其他哥们呢");
try {
barrier.await();//等待其他哥们
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}});
}
exec.shutdown();
}
}
Semaphore
是独占锁的generalization版本,允许一定数量的线程进入而不是一个。
Object.wait() / notifyAll(), Condition.await() / signalAll(),基于条件的Synchronizer
之前的Synchronizer 是基于独占(lock),限流(Semaphore),或者某种计数(CountDownLatch,CylicBarrier),而这组Synchronizer 是基于条件的
有的线程等待某种条件为真才往下执行,有的线程改变这个条件。
基于条件的Synchronizer (wait, notify) 是最基本的,其他的synchronizer都可以用wait / notify自己实现,只是不同synchronizer的条件不一样而已
CountDownLatch 和CyclicBarrier 的条件都是一个计数值,
对于CountDownLatch , 一部分线程调用await等待计数值为0,另一部分线程调用coutDown的线程改变计数值
对于CyclicBarrier 只有await操作,所有线程都调用await,awai操作t既改变计数值,又会等待计数值为0
class CountDownLatch {
int count;
Lock lock;
Condition c;
public CountDownLatch(int x) {
count = x;
lock = new ReentrantLock();
c = lock.newCondition();
}
public void countDown() {
lock.lock();
try {
if (count > 0) --count;
if (count == 0) c.signalAll();
} finally {
lock.unlock();
}
}
public void await() {
lock.lock();
try {
if (count > 0) c.await();
} catch (InterruptedException e){
}
finally {
lock.unlock();
}
}
}
一种有界阻塞队列的实现,条件是queue的 size, 也可以用Semaphore做
class BoundedQueue<T> {
private Deque<T> q = new LinkedList<T>();
private int size;
private Lock lock = new ReentrantLock();
private Condition produceSignal;
private Condition consumeSignal;
public BoundedQueue(int size) {
this.size = size;
produceSignal = lock.newCondition();
consumeSignal = lock.newCondition();
}
public boolean offer(T e) throws InterruptedException {
try {
lock.lock();
while(q.size() == size) {
produceSignal.await();
}
if (q.offer(e)) {
consumeSignal.signal();
return true;
}
return false;
} finally {
lock.unlock();
}
}
public T poll() throws InterruptedException {
try {
lock.lock();
while (q.isEmpty()) {
consumeSignal.await();
}
return q.poll();
} finally {
lock.unlock();
}
}
}
class BoundedQueue2<T> {
Deque<T> queue = new LinkedList<T>();
Semaphore emptySem, fullSem;
public BoundedQueue2(int size) {
emptySem = new Semaphore(size);
fullSem = new Semaphore(0);
}
public void offer(T item) throws InterruptedException {
emptySem.acquire();
if (queue.offer(item))
fullSem.release();
else
emptySem.release();
}
public T poll() throws InterruptedException {
fullSem.acquire();
T ret = queue.poll();
emptySem.release();
return ret;
}
}
比较一下两种实现,1)锁+ 自定义 条件(size) , 2)2个Semaphone,
1)Semaphore把计数的check 和修改在一起作为原子操作, 而方案一需要自己维护lock保护共享数据,并且需要while来循环检查条件,多浪费了些cpu资源
2)Semaphore的方案并发度更细,并没有额外的去保护内置队列同时只被一个线程访问,这个是否有必要,看情景多线程读多线程写的情况是需要保护内置结构的访问的,比如连续的两个写,从Semaphore角度只要没满都可以过,但是同时的插入队尾是有问题的。
3)总结,Semaphore提供的便利类似原子操作CAS,比较和Increment一起作为原子操作。只是确保有资源的情况才允许线程进入,但是没有解决另一个问题,共享数据被安全的访问的问题。