经典问题:生产者和消费者问题详细解析--引出多线程通信和JDK1.5新特性。
按照之前的知识,可以将上面的问题写成:
package ThreadDemo;
import java.util.concurrent.locks.*;
/*
生产者 、消费者案例:
*/
class Resource2//创建一个共享资源:Resource2
{
private String name;
private int count=1;
private boolean flag = false;
public synchronized void set(String name)
{
this.name = name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者...生产"+this.name);
flag = true;
}
public synchronized void out()
{
System.out.println(Thread.currentThread().getName()+"...消费者...消费"+this.name);
flag = false;
}
}
class Producer implements Runnable
{
private Resource2 r;
Producer(Resource2 r)
{
this.r =r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
}
class Consumer implements Runnable
{
private Resource2 r;
Consumer(Resource2 r)
{
this.r =r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
public class ProductAndConsumer {
/**
* @param args
*/
public static void main(String[] args) {
Resource2 r = new Resource2();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
但是,不会出现生产消费循环:结果一个是生产,一个是消费。这时,就引出了等待唤醒机制:
等待唤醒的机制涉及的方法:
1、wait():让线程处于冻结状态,被wait的线程会存储到线程池当中。
线程池:存储线程的容器;
2、notify():用于唤醒线程池当中的一个线程(任意);
3、notifyAll():用于唤醒线程池当中的所有线程;
这些方法都必须定义在同步中,因为,这些方法是用于操作线程状态的方法。
必须要明确到底操作的是哪个锁上的线程。
添加后:
public synchronized void set(String name)
{
while/*if*/(flag)
try{this.wait();}catch (InterruptedException e) {}
this.name = name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者...生产"+this.name);
flag = true;
notifyAll();//由于,不知道是不是会唤醒到消费线程,所以,将所有在线程池中所有的线程唤醒;
}
public synchronized void out()
{
while/*if*/(!flag)
try{this.wait();}catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"...消费者...消费"+this.name);
flag = false;
notifyAll();
}
修改后,输出的结果:
这时,结果又可能会出现下面的这种状况:
Thread-0...生产者...生产烤鸭430286
Thread-2...消费者...消费烤鸭430286
Thread-3...消费者...消费烤鸭430286
Thread-0...生产者...生产烤鸭430287
Thread-2...消费者...消费烤鸭430287
Thread-3...消费者...消费烤鸭430287
多个消费者同时消费同一个产品,这是不允许的。
总结一下上面解决问题的方法:
第一、while判断标记,解决了线程获取执行权后是否要运行的问题。
第二、notifyAll解决了唤醒线程的问题,本方线程一定会唤醒对方线程。
首先将if判断标记改为while,因为if判断只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。将if(flag)和if(!flag)改为while(flag)和while(flag)后,会解决掉上面的问题,但是,修改后会导致死锁的出现。同时,不能唤醒对方的线程。
如果使用notify,它只能唤醒一个线程,如果本方唤醒了本方,内有意义。而且while判断标记+notify会出现死锁问题。
如何解决这个问题?
在Java JDK1.5提供了新的解决形式:
public interface Lock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,
可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
对于同步代码块,对锁的操作是隐式的,而对于JDK1.5之后,使用Lock来对所进行对象封装。
在JDK1.5以后,将同步和锁封装成了对象,并将操作锁的隐式方法定义到了对象当中,并将隐式动作变成了显示动作。
Lock类中方法:
lock() 获取锁;
unlock() 释放锁;
newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。
如果在获取锁和释放锁包围中的代码中有异常,这时,就不会释放锁。锁不会释放,其他线程也不会进入。
所以,必须将unlock()放到finally代码块当中。
在JDK1.5后,将监视器封装成了condition对象。任意Lock对象可以和多组监视器对象进行组合。
Lock类的特点:它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作。
同时更为灵活。可以一个锁上加上多组监视器。
lock():获取锁;
unlock():释放锁。通常定义在finally代码块中。
Condition接口:出现替代了Object类中的wait notify notifyAll方法。
将这些监视器方法单独进行了封装,变成Condition监视器对象。 可以任意锁进行组合。
修改后的程序:
class Resource2
{
private String name;
private int count=1;
private boolean flag = false;
//创建新的锁对象;
Lock lock = new ReentrantLock();
//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name)
{
lock.lock();
try{
while(flag)
try{producer_con.await();}catch (InterruptedException e) {}
this.name = name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者...生产"+this.name);
flag = true;
consumer_con.signal();
}
finally{
lock.unlock();
}
}
public void out()
{
lock.lock();
try{
while(!flag)
try{consumer_con.await();}catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"...消费者...消费"+this.name);
flag = false;
producer_con.signal();
}finally{
lock.unlock();
}
}
class Producer implements Runnable
{
private Resource2 r;
Producer(Resource2 r)
{
this.r =r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
}
class Consumer implements Runnable
{
private Resource2 r;
Consumer(Resource2 r)
{
this.r =r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
public class ProductAndConsumer {
/**
* @param args
*/
public static void main(String[] args) {
Resource2 r = new Resource2();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
/*
多生产、多消费者的问题。
*/
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
通过上面的实现,可以完成一开始的需求。
本文详细解析了经典的生产者和消费者问题,通过多线程通信实现了资源的有效管理和分配。介绍了如何利用Java的synchronized关键字及wait、notify等方法实现线程间的等待唤醒机制,进而避免资源竞争和死锁。此外,还探讨了Java 1.5引入的新特性Lock和Condition接口,以更加灵活的方式解决了多生产者多消费者场景下的问题。

2160

被折叠的 条评论
为什么被折叠?



