什么是多线程之间的通信?
- 多个线程对同一个临界区进行不同的操作
- 常用方法介绍:
- wait()方法, 让当前线程从运行状态变成阻塞状态,并释放占有锁。
- notify()方法,让当前线程从阻塞状态变成可运行状态,可竞争锁。
- notifyAll()方法,让所有线程从阻塞状态变成可运行状态,可竞争锁。
生产者消费者问题
- 生产者消费者是非常经典的线程同步模型,在编程中也有非常多的应用。生产者消费者问题描述如下:一个或者多个生产者往消息队列里面插入数据;单个消费者从消息队列里面取出数据处理;并且对消息队列的操作必须是互斥的。
问题的初步解决
1.定义一个临界区,或者叫缓存区。
class Resouce {
int goodCount = 0 ;
void put(){
goodCount++ ;
}
void get(){
goodCount-- ;
}
}
2.编写生产者模型:
class Productor implements Runnable{
Resouce res ;
public Productor(Resouce res) {
this.res = res ;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
product(res) ;
}
private void product(Resouce res) {
// TODO Auto-generated method stub
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
res.put();
System.out.println("生产者生产了一个商品"+",剩余:"+res.goodCount);
}
}
3.编写消费者模型:
class Consumer implements Runnable{
Resouce res ;
public Consumer(Resouce res) {
// TODO Auto-generated constructor stub
this.res = res ;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
consume(res) ;
}
private void consume(Resouce res) {
// TODO Auto-generated method stub
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
res.get();
System.out.println(Thread.currentThread().getName()+"消费了一个商品"+",剩余:"+res.goodCount);
}
}
4.编写main方法测试
Resouce res = new Resouce() ;
Thread productor = new Thread(new Productor(res)) ;
Thread consumer1 = new Thread(new Consumer(res),"消费者1") ;
Thread consumer2 = new Thread(new Consumer(res),"消费者2") ;
productor.start();
consumer1.start();
consumer2.start();
- 运行结果(节选):
消费者2消费了一个商品,剩余:-1
消费者1消费了一个商品,剩余:-1
消费者2消费了一个商品,剩余:-2
消费者1消费了一个商品,剩余:-2
生产者生产了一个商品,剩余:-1
消费者1消费了一个商品,剩余:-2
消费者2消费了一个商品,剩余:-3
消费者2消费了一个商品,剩余:-5
由运行结果可以看出结果是完全错乱的,这是因为线程是不安全的。
代码改进1
- 为了解决线程安全问题使用synchronize控制进入临界区的线程,保证每次只有一个线程进入临界区。
1.改进的生产者
class Productor implements Runnable{
Resouce res ;
public Productor(Resouce res) {
this.res = res ;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
product(res) ;
}
private synchronized void product(Resouce res) {
// TODO Auto-generated method stub
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
res.put();
System.out.println("生产者生产了一个商品"+",剩余:"+res.goodCount);
}
}
2.改进的消费者
class Consumer implements Runnable{
Resouce res ;
public Consumer(Resouce res) {
// TODO Auto-generated constructor stub
this.res = res ;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
consume(res) ;
}
private synchronized void consume(Resouce res) {
// TODO Auto-generated method stub
if(res.goodCount > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
res.get();
System.out.println(Thread.currentThread().getName()+"消费了一个商品"+",剩余:"+res.goodCount);
}
}
}
- 运行结果(节选):
生产者生产了一个商品,剩余:1
消费者2消费了一个商品,剩余:0
消费者1消费了一个商品,剩余:0
生产者生产了一个商品,剩余:1
消费者2消费了一个商品,剩余:-1
消费者1消费了一个商品,剩余:-1
生产者生产了一个商品,剩余:0
由运行结果可以看出通过加锁并不能完全解决问题。
代码改进2
- 使用线程之间通信的方法来进行同步,从而解决问题。
- 假设生产者线程每100毫秒生产一个产品之后通知所有线程进入临界区notifyAll(),消费者线程每200毫秒在竞争得到线程后进入临界区,否则等待wait(),定义临界区最大容量为5,如果满则生产线程等待。
注意一定要在休眠wait()之前进行唤醒notify()操作,否则会发生死锁
1.改进后的临界区:
class Resouce {
int goodCount = 0 ;
final int MAXSIZE = 5 ;
synchronized void put(){
if(goodCount < MAXSIZE){
goodCount++ ;
System.out.println("生产者生产了一个商品"+",剩余:"+goodCount);
}else{
try {
notify();
wait() ;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
synchronized void get(){
if(goodCount > 0 ){
goodCount-- ;
System.out.println(Thread.currentThread().getName()+"消费了一个商品"+",剩余:"+goodCount);
}else{
try {
notify();
wait() ;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
2.改进后的生产者
class Productor implements Runnable{
Resouce res ;
public Productor(Resouce res) {
this.res = res ;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
res.put();
}
}
}
3.改进后的消费者
class Consumer implements Runnable{
Resouce res ;
public Consumer(Resouce res) {
// TODO Auto-generated constructor stub
this.res = res ;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
res.get();
}
}
}
- 运行结果(节选):
生产者生产了一个商品,剩余:1
消费者1消费了一个商品,剩余:0
生产者生产了一个商品,剩余:1
生产者生产了一个商品,剩余:2
消费者1消费了一个商品,剩余:1
生产者生产了一个商品,剩余:2
生产者生产了一个商品,剩余:3
消费者1消费了一个商品,剩余:2
生产者生产了一个商品,剩余:3
生产者生产了一个商品,剩余:4
消费者1消费了一个商品,剩余:3
生产者生产了一个商品,剩余:4
生产者生产了一个商品,剩余:5
消费者1消费了一个商品,剩余:4
生产者生产了一个商品,剩余:5
消费者1消费了一个商品,剩余:4
消费者2消费了一个商品,剩余:3
由结果可见,利用线程之间的通信方法即睡眠/唤醒机制,可以完美的解决生产者消费者的问题。