在一个锁中使用多个条件(Using multiple conditions in a Lock)
一个Lock可能关联着一个或者多个条件,这些条件表现在Condition接口。这些条件(conditions)的目的是允许多个线程控制一个锁对象并且检查一个条件是真还是假,当一个条件为false时,那么线程将会被挂起,直到其他线程唤醒它;Condition接口提供了挂起一个线程和唤醒一个线程的机制;
在之前的生产者消费者例子中,使用了Lock来同步临界区,生产者和消费者都只有一个线程;在下面的例子中,生产者和消费者都将有多个线程,当缓冲区满时,所有的生产者将会被挂起,消费者负责唤醒生产者;当缓冲区空时,消费者将被挂起,生茶者负责唤醒消费者;
动手实现
(1)创建一个大小固定的队列作为生产者和消费者的缓冲区;
public class MyQueue<T> {
private ReentrantLock lock=new ReentrantLock();
private Condition pullConditon=lock.newCondition();
private Condition pushCondition=lock.newCondition();
private int maxSize;
private LinkedList<T> list=new LinkedList<>();
public MyQueue(int size) {
maxSize=size;
}
public void push(T t){
lock.lock();
try {
while (list.size()== maxSize) {
// Current push thread release lock and sleep.
pushCondition.await();
}
list.push(t);
System.out.printf("%s Push Size %d\n", Thread.currentThread().getName(), list.size());
// Week up all pull thread
pullConditon.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public T pull(){
T t=null;
lock.lock();
try {
while (list.size() == 0) {
// Current poll thread release lock and sleep.
pullConditon.await();
}
t=list.poll();
System.out.printf("%s Pull Size %d\n", Thread.currentThread().getName(), list.size());
//Week up all push threads
pushCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return t;
}
}
(2)创建消费者
public class Consumer implements Runnable{
private MyQueue<Integer> myQueue;
public Consumer(MyQueue<Integer> myQueue) {
this.myQueue = myQueue;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
myQueue.pull();
try {
Random random=new Random();
Thread.sleep(random.nextInt(500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(3)创建生产者
public class Producer implements Runnable{
private MyQueue<Integer> myQueue;
public Producer(MyQueue<Integer> myQueue) {
this.myQueue = myQueue;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
myQueue.push(i);
try {
Random random=new Random();
Thread.sleep(random.nextInt(500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(4)Main,创建3个消费者,3个生产者
public class Main {
public static void main(String[] args) {
MyQueue<Integer> myQueue = new MyQueue<>(100);
Thread[] producer = new Thread[3];
Thread[] consumer = new Thread[3];
for (int i=0;i<producer.length;i++) {
producer[i] = new Thread(new Producer(myQueue));
}
for (int j=0;j<consumer.length;j++) {
consumer[j] = new Thread(new Consumer(myQueue));
}
for (Thread c : consumer) {
c.start();
}
for (Thread p : producer) {
p.start();
}
}
}
由于生产者和消费者一样多,生产者生产的和消费者消费的刚好,所以最终程序会正常结束;
要点
在该例子中,当一个生产者线程占用了锁对象,那么其他的生产者线程和消费者线程都将被挂起,直到该线程释放了锁对象;当队列满时,生产者将执行pushConditon接口的await()方法,执行该方法,该线程将会被挂起,并且同时释放掉锁对象,从而允许其它线程执行;到消费者执行pushConditon的signalAll()方法时(注意这里是signalAll()而不是notifyAll()),将会唤醒被所有被pushCondtion挂起的线程,即唤醒生产者;从这个例子生越来越感觉Java线程机制的强大和灵活;