在synchronized中解决线程间通信的生产者消费者问题可以使用Object类的wait()和notify()/notifyAll()方法(可参考),但是我在《同步之Lock锁》中说过Lock是对synchronized的一种更为面向对象的替代,如果我们使用Lock来解决生产者消费者问题又将怎样编写代码呢?在JDK1.5中提供的Condition配套Lock可以实现相同的功能,Condition中的await()和signal()/signalAll()就相当于Object的wait()和notify()/notifyAll()。相信如果对Object的这三个方法了解的话对Condition的理解也就不难。
不过要注意的是,Condition是被绑定到Lock上的,所以要创建一个Lock的Condition必须使用newCondition()方法。
创建资源对象类Ticket
package com.gk.thread.communication;
public class Ticket {
private String place; // 车票的起始地
private String date; // 车票的日期
private int number; // 车票的数量
public boolean empty = true; // 标记,是否有车票,true表示没有车票
// 省略一系列getXxx()和setXxx()...
}
创建生产者类Producer
package com.gk.thread.communication.lock;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import com.gk.thread.communication.Ticket;
public class Producer implements Runnable{
private Ticket ticket;
private Lock lock;
private Condition condition;
public Producer(Ticket ticket, Lock lock, Condition condition) {
this.ticket = ticket;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
lock.lock(); // 加锁
try {
while(!ticket.isEmpty()) {
condition.await();
}
String place = Thread.currentThread().getName();
String date = new SimpleDateFormat("yyyy-MM-dd HH点mm分").format(new Date());
int number = (int)(Math.random()*10 + 100);
/*
* 设置车票的属性
*/
ticket.setPlace(place);
ticket.setDate(date);
ticket.setNumber(number);
System.out.println("生产了" + number + "张 " + date + " " + place + "的票...\n");
ticket.setEmpty(false); // 生产者生产了票之后就有票了,所以修改标记empty为false
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock(); // 释放锁
}
}
}
创建消费者类Customer
package com.gk.thread.communication.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import com.gk.thread.communication.Ticket;
public class Customer implements Runnable{
private Ticket ticket;
private Lock lock;
private Condition condition;
public Customer(Ticket ticket, Lock lock, Condition condition) {
this.ticket = ticket;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
lock.lock();
try {
while(ticket.isEmpty()) {
condition.await();
}
String place = ticket.getPlace();
String date = ticket.getDate();
int number = ticket.getNumber();
System.out.println("消费了" + number + "张 " + date + " " + place + "的票...\n");
ticket.setEmpty(true); // 消费者消费了票之后就没有票了,所以修改标记empty为true
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}
}
测试代码
package com.gk.thread.communication.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.gk.thread.communication.Ticket;
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Runnable producer = new Producer(ticket, lock, condition);
Runnable customer = new Customer(ticket, lock, condition);
/*
* 多个生产者线程和多个消费者线程
*/
String from = "广州";
String[] to = {"北京", "上海", "长沙", "杭州", "重庆", "西安", "厦门", "拉萨", "西藏", "哈尔滨"};
List place = new ArrayList();
for(int i=0; i " + to[i]);
}
for(String p : place) {
new Thread(producer, p).start(); // 生产者
new Thread(customer).start(); // 消费者
}
}
}
运行结果与《生产者消费者模式之synchronized与Object》这篇博客是一致的,如果不理解,可参考《生产者消费者模式之synchronized与Object》的解释。
上面的例子可以看成是Condition对Lock功能的补充(线程通信中synchronized对应Object的wait()和notify()/notifyAll();Lock对应Condition的await()和signal()/signalAll()),但是Condition的强大之处在于它可以为多个线程间建立不同的Condition,也就是说Condition可以有多路等待和通知。我们知道synchronized的notifyAll()方法是唤醒所有等待的线程,如果有些线程不想唤醒呢?例如,在生产者类中我们就唤醒所有等待的消费者线程,在消费者类中就唤醒所有等待的生产者线程。请看下面代码。
创建消费者类Producer
package com.gk.thread.communication.lock;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import com.gk.thread.communication.Ticket;
public class Producer2 implements Runnable {
private Ticket ticket;
private Lock lock;
private Condition customerCondition;
private Condition producerCondition;
public Producer2(Ticket ticket, Lock lock, Condition customerCondition, Condition producerCondition) {
this.ticket = ticket;
this.lock = lock;
this.customerCondition = customerCondition;
this.producerCondition = producerCondition;
}
@Override
public void run() {
lock.lock(); // 加锁
try {
while(!ticket.isEmpty()) {
producerCondition.await(); // 生产者线程等待
}
String place = Thread.currentThread().getName();
String date = new SimpleDateFormat("yyyy-MM-dd HH点mm分").format(new Date());
int number = (int)(Math.random()*10 + 100);
/*
* 设置车票的属性
*/
ticket.setPlace(place);
ticket.setDate(date);
ticket.setNumber(number);
System.out.println("生产了" + number + "张 " + date + " " + place + "的票...\n");
ticket.setEmpty(false);
customerCondition.signalAll(); // 唤醒所有消费者线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock(); // 释放锁
}
}
}
创建消费者类Customer
package com.gk.thread.communication.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import com.gk.thread.communication.Ticket;
public class Customer2 implements Runnable{
private Ticket ticket;
private Lock lock;
private Condition customerCondition;
private Condition producerCondition;
public Customer2(Ticket ticket, Lock lock, Condition customerCondition, Condition producerCondition) {
this.ticket = ticket;
this.lock = lock;
this.customerCondition = customerCondition;
this.producerCondition = producerCondition;
}
@Override
public void run() {
lock.lock(); // 加锁
try {
while(ticket.isEmpty()) {
customerCondition.await(); // 消费者线程等待
}
String place = ticket.getPlace();
String date = ticket.getDate();
int number = ticket.getNumber();
System.out.println("消费了" + number + "张 " + date + " " + place + "的票...\n");
ticket.setEmpty(true);
producerCondition.signalAll(); // 唤醒所有的生产者线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock(); // 释放锁
}
}
}
测试代码:
package com.gk.thread.communication.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.gk.thread.communication.Ticket;
public class Test2 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Lock lock = new ReentrantLock();
Condition customerCondition = lock.newCondition();
Condition producerCondition = lock.newCondition();
Runnable producer = new Producer2(ticket, lock, customerCondition, producerCondition);
Runnable customer = new Customer2(ticket, lock, customerCondition, producerCondition);
String from = "广州";
String[] to = {"北京", "上海", "长沙", "杭州", "重庆", "西安", "厦门", "拉萨", "西藏", "哈尔滨"};
List place = new ArrayList();
for(int i=0; i " + to[i]);
}
for(String p : place) {
new Thread(producer, p).start(); // 生产者
new Thread(customer).start(); // 消费者
}
}
}
测试结果与上边一样,这里就不放截图了,但是请注意与上边例子不同的是,Producer2有两个Condition,一个是生产者Condition(producerCondition ),一个是消费者Condition(customerCondition ),当生产者等待的时候就调用producerCondition.await(),当生产者要唤醒消费者消费的时候就调用customerCondition.signalAll();Customer2与Producer2正好相反。这样一来,不仅程序的可理解性增强了,也提高了程序的效率,因为现在唤醒的不再是所有等待着的线程了,而是唤醒所有等待着的对方线程。