之所以想认真一步步细述经典的消费者和生产者同步代码,是因为周围的人提及多线程总觉得线程是不可控性,线程同步让人非常费解和更是难以自如应用,另一方面对于wait和notify的总是把握不好,甚至直接把这两个方法写在线程的run方法体内。相信网上也有非常多的例子来实现同步,但这里我会一步步来说明自己的思路,这样可以非常好地理解同步。作为经典的线程同步例子,生产者和消费者模型,我们先抽象出三个类,生产者类Producer, 消费者Consumer, 仓库类StoreHouse。既然要同步总得有一个信号量,这个信号是核心和关键所在。wait和notify都是去响应这个信号量而生的,信号量可以是一个变量,也可以是一种条件触发。这里把信号量设置成一个变量。这里可以定义为available。对于仓库来说,当然会有两个方法,存货和取货,存的时候,不想取的人等在边上催;而取货的时候,也不想存货的人来打扰自己,那么仓库管理员就先把门锁上之后,再进行操作,这就是同步锁。那么程序的小框架:
public class Demo {
class StoreHouse {
private boolean available = true;
public synchronized void put(){
}
public synchronized int get(){
}
}
class Producer {
}
class Consumer {
}
public static void main(String[] args) {
}
}
仓库门外有个牌子,写着“有货”和“缺货”。这个牌子是一个信号量,是线程同步时阻塞与唤醒的条件,是同步的核心,有时候,大家实现多线程同步程序出错,就是因为没有理清信号量的赋值与判断,本题中信号量是由同步锁对象来控制和保持,实际程序应用中,也可以在调用多线程同步程序时,设置两个可以相关的条件,如一个线程 if(i<5) storeHouse.put(1); 另一个线程 if(i>10) storeHouse.get(); 也可以达到同步的效果,关键是两个条件在各自线程内都是可达条件,就是说在某种情况下是可以满足的。当然,如果两个线程之间只是一种交叉执行,并没有一定要谁先执行的时候,可以没有任何信息量,仅在同步方法里。{ notify; ........具体代码; wait;} 当然最好是只有一个对象锁,也只有两个线程。这依赖于具体情况具体分析。对于这个模型问题来说,一定要有信号量的,因为消费者到没有存任何东西的仓库取东西和生产者到存有东西的仓库里存东西,这两种情况是不合情理的。
现在的问题,无论对于消费者,还是生产者,都不知道仓库里到底有没有货,他们兴冲冲地跑过来,可能刚好,消费者过来有货拿、生产者过来有地方可以放货。但有时可能很不巧。消费者过来,发现“缺货”,他没有辙,只有到仓库管理员打电话,管理员说,“你等着!”它就安耽地在门外睡觉了。一旦生产者一到货,管理一脚踢醒他,可以拿货了。同理,对于生产者来说,也会碰到“有货”的情形,他不能接着生产,只有等着人家提货以后,才有继续。也就是说,让人等,通知别人都是仓库应该做的事情。再扩展下这两个提和取的方法:
public synchronized void put() throws InterruptedException{
if(available == true) wait();
System.out.println("已放入");
notify();
available = true;
}
public synchronized void get() throws InterruptedException{
if(available == false) wait();
System.out.println("已提取");
notify();
available = false;
}
对于主程序来说,肯定要新建三个对象:仓库对象、生产者对象、消费者对象,然后,生产者对象和消费者对象都共享一个仓库对象,那么如何共享一个仓库呢,就是传引用进去,注意:引用传递的结果,就是这些句柄都指向同一个对象。那么修改两个构造方法,当然他们是执行线程就必须去继承Thread类:
class Producer {
private StoreHouse storeHouse = null;
public Producer(StoreHouse storehouse){
this.storeHouse = storehouse;
}
}
class Consumer {
private StoreHouse storeHouse = null;
public Consumer(StoreHouse storehouse){
this.storeHouse = storehouse;
}
}
那么main方法就可以写成这样了:
public static void main(String[] args) {
StoreHouse storeHouse = new StoreHouse();
Producer producer = new Producer(storeHouse);
Consumer consumer = new Consumer(storeHouse);
producer.start();
consumer.start();
}
当然这时,虽然线程被启动起来,但不会显示任何操作结果,因为线程的run方法内部是空的。在run中只需要加入:storeHouse.put(); 和 storeHouse.get(); 程序可运行代码:
class StoreHouse {
private boolean available = false;
public synchronized void put() throws InterruptedException{
if(available == true) wait();
System.out.println("已放入");
notify();
available = true;
}
public synchronized void get() throws InterruptedException{
if(available == false) wait();
System.out.println("已提取");
notify();
available = false;
}
}
class Producer extends Thread{
private StoreHouse storeHouse = null;
public Producer(StoreHouse storehouse){
this.storeHouse = storehouse;
}
public void run(){
while(true){
try {
storeHouse.put();
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
class Consumer extends Thread {
private StoreHouse storeHouse = null;
public Consumer(StoreHouse storehouse){
this.storeHouse = storehouse;
}
public void run(){
while(true){
try {
storeHouse.get();
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public class Demo {
public static void main(String[] args) {
StoreHouse storeHouse = new StoreHouse();
Producer producer = new Producer(storeHouse);
Consumer consumer = new Consumer(storeHouse);
producer.start();
consumer.start();
}
}
执行结果:
已提取
已放入
已提取
已放入
交替执行。这就是我们预期的同步结果,当然,现在很多同步代码是有序号的,就是说放入No.9的东西,取出也是No.9的东西,那只是在两个构造方法里略作改动即可,不过,以上已经实现了同步代码的核心功能。下边的完整的代码+注释:
/**
* @author Gabe
* 仓库类,定义了到仓库里取货和存货的两个方法
*/
class StoreHouse {
int number = 0; //放置货物编号
boolean available = false; //true代表”有货“,false代表”缺货“
public synchronized void put(int number) throws InterruptedException{
//put是Producer生产者放置货物调用的方法,如果为true表示无法再存货到仓库中,必须等待
if(available == true) wait();
System.out.println("已放入:" + number);
//这里notify()也可以放到本方法最后一行,notify()并非是如wait()一样马上让出线程执行权,
//它必须本方法执行完,再随机唤醒一个正在wait list里的线程.
notify();
this.number = number;
//这里必须置为”有货”,因为生产者经过等待已经执行了本方法,它给number赋值了,说明他已经放一个新的货物进去。
available = true;
}
public synchronized void get() throws InterruptedException{
//get是Consumer消费者取货物调用的的方法,如果为false表示仓库中“缺货”,必须等待
if(available == false) wait();
System.out.println("已提取:"+this.number);
notify();
//这里必须重新置为”缺货”,因为消费者经过等待已经执行了本方法,提取走了货物。
available = false;
}
}
/**
* @author Gabe
* 生产者线程类
*/
class Producer extends Thread{
private StoreHouse storeHouse = null;
//构造方法, 把仓库引用传给本类数据成员域
public Producer(StoreHouse storehouse){
this.storeHouse = storehouse;
}
//多线程的调用方法
public void run(){
//生产10个产品
for(int i= 1; i<= 10; i++){
try {
//调用仓库类的同类方法
storeHouse.put(i);
} catch (InterruptedException e) { e.printStackTrace(); }
}
//这个命令退出所有线程
System.exit(0);
}
}
class Consumer extends Thread {
private StoreHouse storeHouse = null;
//构造方法, 把仓库引用传给本类数据成员域
public Consumer(StoreHouse storehouse){
this.storeHouse = storehouse;
}
//多线程的调用方法
public void run(){
while(true){
try {
//调用仓库的同步方法get()
storeHouse.get();
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public class Demo {
public static void main(String[] args) {
//新建一个仓库对象
StoreHouse storeHouse = new StoreHouse();
//新建一个生产者和消费者对象,并把已生成仓库对象通过构造方法传给这两个对象
Producer producer = new Producer(storeHouse);
Consumer consumer = new Consumer(storeHouse);
producer.start();
consumer.start();
}
}
执行结果:
已放入:1
已提取:1
已放入:2
已提取:2
已放入:3
已提取:3
已放入:4
已提取:4
已放入:5
已提取:5
已放入:6
已提取:6
已放入:7
已提取:7
已放入:8
已提取:8
已放入:9
已提取:9
已放入:10
已提取:10