线程之间通信
当一个线程正在使用一个同步方法时,其他线程就不能使用这个同步方法,因为被上了锁。对于同步方法,有时候会涉及某些特殊的情况,例如生活中一个人在排队买电影票时,如果他给售票员的钱不是零钱,而售票员没有零钱找零给他,那么他就需要等待,并允许后面的其他人买票,以便售票员获得零钱给他。Java语言包含了wait( ),notify( )和notifyAll( )方法协调线程之间的进行进度关系,从而实现线程同步。这些方法在对象中是用final方法实现的,所以所有的类都含有它们,且不能被重写。这三个方法仅在synchronized方法中才能被调用。
- wait( ) 导致当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法,与sleep不同,会释放锁。
- notify( ) 恢复相同对象中第一个调用 wait( ) 的线程。
- notifyAll( ) 恢复相同对象中所有调用 wait( ) 的线程。具有最高优先级的线程最先运行。
例子
模拟喜羊羊和懒羊羊买电影票,售票员只有两张5元的钱,电影票5元一张,喜羊羊拿了20元一张的钱排在懒羊羊前面买票,懒羊羊拿一张5元的钱买票,因此喜羊羊必须要等待懒羊羊先买票。
//售票处
class TicketsHouse implements Runnable{
int fiveAmount=2;//2张5元
int twentyAmount=0;//0张20元
public void run() {
if(Thread.currentThread().getName().equals("喜羊羊")) {
saleTickets(20);
}else if(Thread.currentThread().getName().equals("懒羊羊")) {
saleTickets(5);
}
}
//买票
private synchronized void saleTickets(int money) {
if(money == 5) {
fiveAmount = fiveAmount+1;
System.out.println(Thread.currentThread().getName()+"钱刚好,买到了电影票");
}else if(money == 20) {
while(fiveAmount<3) {
try {
System.out.println(Thread.currentThread().getName()+"靠边等待...");
wait();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"继续卖票");
} catch (InterruptedException e) {}
}
fiveAmount = fiveAmount-3;
twentyAmount = twentyAmount+1;
System.out.println(Thread.currentThread().getName()+"给20,找零15,拿到票 ");
}
notifyAll();
}
}
public class Tickets{
public static void main(String[] args) {
TicketsHouse officer = new TicketsHouse();
Thread xyy = new Thread(officer,"xyy");
Thread lyy = new Thread(officer,"lyy");
xyy.start();
lyy.start();
}
}
运行结果:
喜羊羊靠边等待...
懒羊羊钱刚好,买到了电影票
喜羊羊继续买票
喜羊羊给20,找零15,拿到票
生产者消费者模式
应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费;
- 如果仓库中没有产品,那么生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者拿走为止;
- 如果仓库中有产品,那么消费者可以将产品取走,否则停止消费并等待生产,知道仓库中放入产品为止。
生产者和消费者共同分享一个资源,对于生产者,在没有生产产品之前,要通知消费者等待,等生产好产品之后,要通知消费者消费。对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产商品。它不是一种设计模式,而是一种解决由多线程引发的同步问题的办法,也称为有限缓冲问题。
synchronized可以组织并发更新同一个资源,实现同步,但是不能实现线程之间的通信。
解决方式1:管程法
生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
缓冲区:消费者不能直接使用生产者的数据,它们之间存在“缓冲区”,生产者将生产出来的数据放入“缓冲区”,消费者从“缓冲区”将数据取出处理。
package com.sxt.thread;
/**
* 生产者消费者实现方式一:管程法
* @author liwenlong
*
*/
public class CoTest01 {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Constomer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
@Override
public void run() {
//生产
for(int i = 0;i<100;i++) {
System.out.println("生产第"+i+"个冰淇淋");
container.push(new Iscream(i));
}
}
}
//消费者
class Constomer extends Thread{
SynContainer container;
public Constomer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
//消费
for(int i = 0;i<100;i++) {
System.out.println("消费第"+container.getIscream().id+"个冰淇淋");
}
}
}
//缓冲区
class SynContainer{
Iscream[] iscreams = new Iscream[10];
int num = 0; //计数器
//存储 生产
public synchronized void push(Iscream iscream) {
//什么时候生产
//容器没有空间,不可以生产
if(num==iscreams.length) {
try {
this.wait();//线程阻塞 消费者通知生产解除
} catch (Exception e) {
// TODO: handle exception
}
}
//存在空间,可以生产
iscreams[num]=iscream;
num++;
//通知生产
this.notifyAll();
}
//获取 消费
public synchronized Iscream getIscream() {
//何时消费 判断容器中是否有数据
//没有数据只有等待
if(num == 0) {
try {
this.wait(); //线程阻塞 生产者通知消费,解除阻塞
} catch (Exception e) {
}
}
//存在数据可以消费
num--;
Iscream iscream = iscreams[num];
this.notifyAll(); //唤醒对方生产
return iscream;
}
}
//冰淇淋
class Iscream{
int id;
public Iscream(int id) {
super();
this.id = id;
}
}
解决方式2:红绿灯法
置一个标识,当标识为真的时候,消费者消费,生产者等待。当标识为假的时候,生产者生产,消费者等待.这种模式像是设置了一个信号灯,因此被称为红绿灯法。