需要三个对象:生产者,消费者,以及容器
作用:解耦
学习编程最主要注重的就是思想+动手能力。
法一:管程法
public class TestPC {
public static void main(String[] args) {
SynContainer c = new SynContainer();
new Producer(c).start();
new Customer(c).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container) {
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"产品。");
container.push(new Product(i));
}
}
}
//消费者
class Customer extends Thread{
SynContainer container;
public Customer(SynContainer container) {
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了"+i+"产品。");
container.pop();
}
}
}
//产品
class Product{
int id;//产品id
public Product(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Product[] products = new Product[10];
int count = 0;
//生产者放入产品
public synchronized void push(Product p) {
if(count==products.length) {
//通知消费者进行消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果没有满,生产产品
products[count]=p;
count++;
//生产出产品,通知消费者消费
this.notifyAll();
}
//消费者 消费产品
public synchronized Product pop() {
//判断能否消费
if(count==0) {
//等待生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果可以消费
count--;
Product p=products[count];
//产品已经买完,通知生产者
this.notifyAll();
return p;
}
}
法二:信号灯法
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Person(tv).start();
}
}
//生产者---演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0) {
this.tv.play("RunningMan"+i);
}else {
this.tv.play("演员休息了"+i);
}
}
}
}
//消费者---观众
class Person extends Thread{
TV tv;
public Person(TV tv){
this.tv = tv;
}
@Override
public void run() {
for(int i = 0; i< 20;i++) {
this.tv.watch();
}
}
}
//产品--节目
class TV{
//演员表演,观众等待 T
//观众等待,演员表演 F
String program;//节目
boolean flag = true;
//表演
public synchronized void play(String program) {
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("演员表演了:"+program);
//通知观众观看
this.notifyAll();
this.program = program;
this.flag = !this.flag;
}
//观看
public synchronized void watch() {
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:"+program);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
但是这种并不是安全的,在官方文档中,wait()与while结合使用。
用while的原因是:假定线程a和线程b均因为产品数量为0而进入wait方法,但是在生产者进行产品生产后,调用notifyAll(),a和b均被唤醒,用if,并不会判定条件,在一个消费产品后会导致另外下按成一个出现安全问题。这个问题就是虚假唤醒,官方文档如下:
/*线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。
虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。
换句话说,等待应该总是出现在循环中,就像这样: */
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}