上一节写了关于银行存取款的例子,在这个例子中还存在一个问题是,当账户余额不够了怎么办?那就需要等待存入足够的钱后再处理。
为了更清楚描述该类问题,用以下代码进行展示:
Queue类起一个容器的作用,其中属性n表示生产者生产的数量,生产者生产一个(n+1),消费者取一个。
package java_thread;
public class Queue {
private int n;
public synchronized int getN() {
System.out.println("消费:" + n);
return n;
}
public synchronized void setN(int n) {
System.out.println("生产:" + n);
this.n = n;
}
}
生产类:
package java_thread;
public class Producer implements Runnable {
Queue queue;
public Producer(Queue queue){
this.queue = queue;
}
@Override
public void run() {
int i = 0;
while(true){
queue.setN(i ++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费类:
package java_thread;
public class Consumer implements Runnable {
Queue queue;
public Consumer(Queue queue){
this.queue = queue;
}
@Override
public void run() {
while(true){
queue.getN();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类:
package java_thread;
public class QueueTest {
public static void main(String[] args) {
Queue queue = new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
输出:
生产:0
消费:0
生产:1
消费:1
生产:2
消费:2
生产:3
消费:3
消费:3
生产:4
消费:4
生产:5
消费:5
生产:6
消费:6
生产:7
消费:7
生产:8
消费:8
生产:9
消费:9
生产:10
消费:10
生产:11
消费:11
生产:12
消费:12
生产:13
可以看到,当n为3时,消费了2次,此时代码还是有问题的。
继续处理产生的问题,定义一个boolean类型的变量,并体会以下三个方法的使用:
- wait()方法∶中断方法的执行,使线程等待
- notify()方法∶唤醒处于等待的某一个线程,使其结束等待
- notifyAll()方法∶唤醒所有处于等待的线程,使它们结束等待
Queue类代码优化如下:
public class Queue {
private int n;
boolean flag = false;
public synchronized int getN() {
if(!flag){ //当flag=false时,表示没有生产,需要等待
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("消费:" + n);
flag = false; //消费完毕,容器中没有数据
return n;
}
public synchronized void setN(int n) {
if(flag){ //当flag=true时,表示前一个生产的还未被取走,需要等待
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("生产:" + n);
this.n = n;
flag = true; //生产完毕,容器中已经有数据
}
}
再次测试输出:
会发现没有上面的问题了,但是出现了停止输出的情况。
此时就会使用到 notify()方法 或 notifyAll()方法 了。
一般使用notifyAll(),因为 notify()方法 会唤醒任意一个处于等待的线程,唤醒的不一定是当下需要执行的线程,而 notifyAll() 会唤醒所有处于等待的线程。
public synchronized int getN() {
if(!flag){ //当flag=false时,表示没有生产,需要等待
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("消费:" + n);
flag = false; //消费完毕,容器中没有数据
notifyAll(); //唤醒所有处于等待的线程
return n;
}
public synchronized void setN(int n) {
if(flag){ //当flag=true时,表示前一个生产的还未被取走,需要等待
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("生产:" + n);
this.n = n;
flag = true; //生产完毕,容器中已经有数据
notifyAll();
}
再次测试输出就没有什么问题了。