这个是一个典型的线程问题。生成者(Productor)生产商品并交给店员(Clerk),消费者(Customer)从店员处购买商品,店员一次性只能购买固定数量的产品,如果生产者试图生成更多的商品,店员会叫生产者暂停一下,如果店里仓库有空位再通知生产者继续生产,如果店中没有商品了,就会告诉消费者等一下,如果店中有商品了就通知消费者来购买。这个场景和我们实际情况也比较贴切,这样就会想可能存在如下问题:
- 生产者比消费者快的时,消费者会漏掉部分数据取不到(产品过剩导致浪费,出现滞销)
- 消费者比生产者快时,消费者会取到相同的数据(产量不足,供不应求)
在解决这个方法之前先介绍一下线程通信
所谓线程通信可以这样理解:线程与线程之间不是相互独立的个体,它们彼此之间需要相互通信和协作,最典型的例子就是生产者-消费者问题:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。
1.notify/wait
前面的文中通过包括 static synchronized 等手段来解决数据共享的问题,即多个线程主动地读取一个共享数据,通过 同步互斥访问机制保证线程的安全性。等待/通知机制主要由Object类中的wait()、notify() 和 notifyAll()三个方法来实现。
wait():令当前线程挂起并放弃CPU、同步资源,让其他线程可以访问并修改共享资源,而当前现场排队等候以获得再次对资源的访问机会
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll():唤醒正在排队等待资源的所有线程结束等待。
注意:
- 这三个方法均非Thread类中所声明的方法,而是Object类中声明的方法。原因是每个对象都拥有monitor(锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作,而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
- Java.lang.Object提供的这三个方法只能在synchronized方法或synchronized代码块中使用
package blog;
public class TestAccount {
public static void main(String[] args) {
Account account = new Account();
Account account1 = new Account();
//多线程对象
User u_weixin = new User(account, 2000);
User u_zhifubao = new User(account, 2000);
Thread weixin = new Thread(u_weixin,"微信账户");
Thread zhifubao = new Thread(u_zhifubao,"支付宝账户");
weixin.start();
zhifubao.start();
}
}
class Account{
public static int money = 3000;
public void takeMoney5(int m,Account a) {
synchronized(a){//表示当前对象的代码被加入了synchronized同步锁,this表示当前对象
String name = Thread.currentThread().getName();
//如果是微信操作则等待,等支付宝操作完成再给微信操作
if (name.equals("微信账户")) {
try {
a.wait();//当前线程等待进入阻塞状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (m > money) {
System.out.println(name + "操作,账户金额不足:"+money);
}else {
System.out.println(name + "操作,账户原有金额:"+money);
System.out.println(name + "操作,取款金额:" + m);
money = money - m;
System.out.println(name + "操作,取款后的余额:" + money);
}
if (name.equals("支付宝账户")) {
try {
a.notify();//唤醒当前优先级最高的线程,进入就绪状态
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class User implements Runnable{
Account account;
int money;
public User(Account account,int money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
account.takeMoney5(money,account);
}
}
输出结果如下:
2.生产者/消费者
结合上述介绍的notify/wait机制,下面来实现生产者和消费者
package day15;
public class Test3 {
public static void main(String[] args) {
Clerk c = new Clerk();
//消费时不生产,生产时不消费
//生产者
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c) {
while (true) {
//无限循环表示无限生产
if (c.productNum == 0) {
System.out.println("商品数为0,开始生产");
while (c.productNum < 5) {
c.productNum++;//生产之后,增加商品
System.out.println("库存值:"+c.productNum);
}
System.out.println("产品数为:" + c.productNum +"结束生产");
c.notify();//唤醒消费者,让生产者线程等待
}else {
try {
c.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
},"生产者").start();
//消费时不生产,生产时不消费
//生产者
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c) {
while (true) {
//无限循环表示无限消费
if (c.productNum == 5) {
System.out.println("商品数为4,开始消费");
while (c.productNum > 0) {
c.productNum--;//消费产品,产品减少
System.out.println("库存值:"+c.productNum);
}
System.out.println("产品数为:" + c.productNum +"结束消费");
c.notify();//唤醒生产者,让消费者线程等待
}else {
try {
c.wait();//消费者线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
},"消费者").start();
}
}
class Clerk{
public static int productNum = 0;
}
输出结果如下: