一个线程进入临界区,却发现它必须等待某个条件满足后才能执行,则要使用一个条件变量来管理那些已获得锁却不能开始执行有用的工作的线程。
看个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedBuffer {
// 可重入的互斥锁
final Lock lock = new ReentrantLock();
// 表示"缓冲区没满"条件
final Condition notFull = lock.newCondition();
// 表示"缓冲区非空"条件
final Condition notEmpty = lock.newCondition();
// 存储空间
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
//锁住原子操作单元
lock.lock();
try {
/*
* 发现缓冲区已满,则阻塞当前线程,放弃锁;
* 以便其它线程能向缓冲区中take数据。
*
* 一旦一个线程调用了await方法,它就进入等待该条件集,
* 当锁可获得时,线程不能立刻解除阻塞,
* 它维护阻塞状态直到另一个线程调用同一个条件上的
* signal(唤醒其中的一个等待线程)方法或signalAll(唤醒所有等待线程)方法
*
* 一旦线程被唤醒,从而有一个线程将获得锁并从它被阻塞的地方继续执行。
* 此时,线程应该再次测试条件,因为现在还不能确定条件一定已经满足。
* signal/signalAll方法仅仅是通知等待的线程:现在条件有可能已经满足了,值得再去测试一下条件。
* 因此,为await的调用应该总是在while等循环中。
*
*/
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length)
putptr = 0;
++count;
/*
* 特别注意:对signalAll的调用不会立即激活等待线程。它只是解除等待线程的阻塞状态。
* 这样这些线程就可以在当前线程退出同步方法后,通过竞争获得对对象的访问。
*
* signal则是随机解除等待集中的某个线程的阻塞状态,如果被随机选中的线程发现自己还是
* 无法运行,它会再次阻塞,如果没有任何其它线程再次调用signal方法,则系统死锁。
*/
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length)
takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
int threadCount = 5;
BoundedBuffer b = new BoundedBuffer();
for(int i=0;i<threadCount;i++)
{
if(i%2 == 0)
new PutThread(b).start();
new TakeThread(b).start();
}
}
public static class PutThread extends Thread
{
private BoundedBuffer b;
private int put = 0;
public PutThread(BoundedBuffer b)
{
this.b = b;
}
@Override
public void run() {
try {
String data = null;
while(true)
{
data = "data" + (++put);
System.out.println(data);
b.put(data);
Thread.sleep(100);
}
} catch (InterruptedException e) {
//e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
public static class TakeThread extends Thread
{
private BoundedBuffer b;
public TakeThread(BoundedBuffer b)
{
this.b = b;
}
@Override
public void run() {
try {
while(true)
{
System.out.println(b.take());
}
} catch (InterruptedException e) {
//e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
}
总结lock和condition的关键点:
1.锁用来保护代码片断,任何时刻只允许一个线程执行被保护的代码。
2.锁可以管理试图进入被保护代码段的线程。
3.锁可以拥有一个或多个相关的条件对象。
4.每个条件对象管理那些已进入被保护代码段但还不能运行的线程。