notify()与wait()
先来说一下notify()、notifyAll()与wait()方法.
可能会令初学者可能比较困惑的是,作为线程通讯的方式,notify()、notifyAll()与wait()却被定义在Object类上。其实很好理解,wait()的本质是释放已获取的锁对象并阻塞等待被唤醒,而notify()则是释放已获取的锁对象并唤醒在wait()中等待同一个锁的线程。因此调用方式便是
[锁对象].wait()
或者
[锁对象].notify().
若不指明调用对象,实际上就用this关键字代替了[锁对象],即当前对象本身作为锁对象。
若调用上述代码时并未获取任何锁,或者指定的锁对象和获取的锁不符,都会抛出 java.lang.IllegalMonitorStateException 生产者者与消费者
生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据。
需要如下三部分组成:
1.存储对象:即图中的Shared Buffer。存储对象中防止生产者的产品,同时让消费者消费。
2.生产者,负责生产商品并添加至存储对象。当存储放满时停止生产。当生产完一个产品时,通知消费者消费。 注意,这里容易产生一个误解:通知消费者消费,并不代表消费者一定会去消费。有可能还未进入消费的代码块时,CPU又可能由于分时的工作原理,放下消费者转而继续执行生产者。又或者可能有多个生产者同时生产。
3.消费者,负责消费商品。当存储对象中商品为空时等待。
实现方式,根据线程通讯操作所处位置,可分为如下两种:
(一) 在消费者和生产者中完成线程通讯
public class Repository { //仓储类
private LinkedList<Object> repository = new LinkedList<Object>(); //仓储实现
private int MAX = 10; //最大商品数量
private int count=0; //当前商品数量
public boolean add(Object product){
count++;
return this.repository.add(product);
}
public Object remove(){
count--;
return this.repository.removeLast();
}
public int getMAX() {
return MAX;
}
public int getCount() {
return count;
}
}
public class Producer implements Runnable{
private Repository repository;
public Repository getRepository() {
return repository;
}
public void setRepository(Repository repository) {
this.repository = repository;
}
public Producer(Repository repository){
this.repository=repository;
}
@Override
public void run() {
while (true) {
synchronized (repository) {
try {
while (repository.getCount()==repository.getMAX()) {
//当商品数量已满时,暂停生产
System.out.println("respositry is full , please wait");
repository.wait();
}
Object newOb = new Object();
if (repository.add(newOb)) { //生成商品,并唤醒所有该锁的等待者
System.out.println("Producer put a Object to respositry");
Thread.sleep((long) (Math.random() * 3000));
repository.notifyAll();
}
} catch (InterruptedException ie) {
System.out.println("producer is interrupted!");
}
}
}
}
public class Consumer implements Runnable{
public Repository respository;
public Repository getRespository() {
return respository;
}
public void setRespository(Repository respository) {
this.respository = respository;
}
public Consumer(Repository repository){
this.respository=repository;
}
@Override
public void run() {
while (true) {
synchronized (respository) {
try {
while (respository.getCount() == 0) {
//当商品为空时,暂停消费
System.out.println("repositry is empty , please wait");
respository.wait();
}
respository.remove(); //消费商品
System.out.println("Comsumer get a Object from repositry");
Thread.sleep((long) (Math.random() * 3000));
respository.notifyAll();
} catch (InterruptedException ie) {
System.out.println("Consumer is interrupted");
}
}
}
}
}
public class Client {
public static void main(String[] args) {
Repository repository=new Repository();
new Thread(new Producer(repository)).start();
new Thread(new Consumer(repository)).start();
}
}
Producer put a Object to respositry
Producer put a Object to respositry
Comsumer get a Object from repositry
Comsumer get a Object from repositry
repositry is empty , please wait
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
Producer put a Object to respositry
respositry is full , please wait
Comsumer get a Object from repositry
Comsumer get a Object from repositry
(二) 在仓储中封装线程通讯操作
C把线程通讯代码放置于消费者和生产者的逻辑中,是否让人觉得有些不够清晰? 没问题,可以在仓储中封装包含线程通讯的新增和移除操作。这样生产者和消费者的代码逻辑中,便不再需要线程通讯和同步的相关代码。
为避免冗余代码太多,下面仅贴出仓储及消费者的代码。客户端代码不变,而生产者代码的变化类似于消费者。
public class Repository {
private LinkedList<Object> repository = new LinkedList<Object>();
private int MAX = 10;
private int count = 0;
public synchronized void add(Object product) {
try {
while (this.count == MAX) {
System.out.println("full wait");
wait();
}
count++;
this.repository.add(product);
System.out.println("producer add a new product");
Thread.sleep((long) (Math.random() * 3000));
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void remove() {
try {
while (this.count == 0) {
System.out.println("empty wait");
wait();
}
count--;
System.out.println("consumer consume a product");
this.repository.removeLast();
Thread.sleep((long) (Math.random() * 3000));
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public int getMAX() {
return MAX;
}
public int getCount() {
return count;
}
}
public class Consumer implements Runnable {
public Repository repository;
public Repository getRespository() {
return repository;
}
public Consumer(Repository repository) {
this.repository = repository;
}
@Override
public void run() {
while (true) {
repository.remove();
}
}
}
是不是看着清爽很多。
用jdk类库简化代码
首先来看一篇网友的文章。
http://www.cnblogs.com/jackyuj/archive/2010/11/24/1886553.html
文中的BlockingQueue是否看着就像是上文中Repository的一个封装实现? 有了这个工具类,对于一些简 单场景就不用重复造轮子了。限于篇幅,就先不在这里贴代码了。若有兴趣可以参考以下链接。
http://yanxuxin.iteye.com/blog/583645
总结
消费者生产者模式可细化的内容不止如此,包括读写锁分离以此提高性能、生产速度和消费速度相匹配等问题。这部分内容以后和JDK1.5新出现的Lock对象以及部分BlockingQueue源码分析一起来说明。