关于Java多线程------(4,经典案例---生产者和消费者)
场景很简单,有一个生产者,负责不断的生产信息,然后将信息放在一个地方,还有一个消费者,负责不断的取走生产者生产出来的信息;
分析一下,很简单,一个信息类,一个生产者线程,一个消费者线程,是的就是这样,生产者和消费者是通过这个信息类关联起来的。
class Info{ // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.setName("李兴华") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("JAVA讲师") ; // 设置内容
flag = false ;
}else{
this.info.setName("mldn") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("www.mldnjava.cn") ; // 设置内容
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.info.getName() +
" --> " + this.info.getContent()) ;
}
}
};
public class ThreadCaseDemo01{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
输出结果:
发现问题可以发发现,info中的信息不对应了,info1中的信息跑到了info2中去了,info2中的也跑到了info1中了,而且,这些info也存在重复取的现象,怎么处理这些问题呢?怎么解决这个问题呢?
分析:
为什么会出现这个问题呢?为什么会出现数据混淆在一起的现象呢?其实主要是因为存在延迟的缘故导致不同步,那么看起来,解决方案就很简单了,加入同步就可以解决问题了呀。。。。。。
加入同步解决问题,验证一番—>
class Info{ // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
public synchronized void set(String name,String content){
this.setName(name) ; // 设置名称
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 设置内容
}
public synchronized void get(){
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
this.info.get() ;
}
}
};
public class ThreadCaseDemo02{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
分析
果然不负所托,解决了数据错乱的问题啊。。。。。但是似乎还是不完美啊,还是存在数据重复取的问题,要求的效果是:—-设置一个值,取得一个值——这个问题,如何解决呢?
如何解决数据重复取的问题?
要解决这个问题,需要一种新的机制:
生产者,生产产品,放在一个地方,消费者从这个地方取走产品;要求是,生产者生产一个产品,消费者取走一个产品,消费者没取走已经生产出来的那个产品的话,生产者不可以生产出新的产品;同样生产者没有生产出新的产品,消费者也拿不到产品,这样一来就控制了生产和消费,就可以达到生产一个取走一个的要求了;
生产者生产出一个新的产品,放在一个地方,这个地方有一个标识灯,
如果灯为绿色,表示这个存放产品的地方现在没有待消费的产品,之前的产品已经被消费者取走了,对消费者来说,不能去取产品了,因为灯为绿色,里面没有产品可以取出来了,对生产者来说,可以生产了,可以把生产的产品放进去了;
如果灯为红色,表示,存放产品的地方有等待消费的产品,对消费者来说,可以进行消费,有产品可以被取走,对生产者来说, 不可以生产,不可以把产品放进去,因为里面有没有被取走的产品,是放不进去的。
实现这样的机制,其实是需要使用到Object类对线程的支持操作的—-等待与唤醒
Object类是所有类的父类,类中有几个方法是对线程操作的支持
1,线程等待:
public final void wait()
throws InterruptedException
指定线程等待的最长时间,以毫秒为单位
public final void wait(long timeout)
throws InterruptedException
指定线程等待的最长时间,毫秒以及纳秒
public final void wait(long timeout,
int nanos)
throws InterruptedException
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
2,唤醒线程
唤醒一个等待的线程
public final void notify()
Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.
This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways:
1,By executing a synchronized instance method of that object.
2,By executing the body of a synchronized statement that synchronizes on the object.
3,For objects of type Class, by executing a synchronized static method of that class.
Only one thread at a time can own an object's monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of this object's monitor.
唤醒全部等待的线程
public final void notifyAll()
Wakes up all threads that are waiting on this object's monitor. A thread waits on an object's monitor by calling one of the wait methods.
The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.
This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of this object's monitor.
直接修改info类,增加等待和唤醒的功能即可。
解决数据重复取的问题——》实践
class Info{ // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
private boolean flag = false ; // 设置标志位
public synchronized void set(String name,String content){
if(!flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
this.setName(name) ; // 设置名称
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 设置内容
flag = false ; // 改变标志位,表示可以取走
super.notify() ;
}
public synchronized void get(){
if(flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
flag = true ; // 改变标志位,表示可以生产
super.notify() ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
this.info.get() ;
}
}
};
public class ThreadCaseDemo03{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
发现,没问题,没有出现数据混乱,也没有出现重复取值的问题,很完美的实现了需求的效果。。。。。
总结:
1,在上面的程序中需要注意,生产者需要不断的生产,但是不能生产处错误的信息,也补鞥呢重复生产,消费者要不断的取走,但是不能重复取走。
2,这个需求,需要Object类中对线程的支持:
等待–>wait()方法。
唤醒—>notify()、notifyAll()方法,
3,涉及到同步、等待、唤醒机制。
尊重知识产权,本博客内容学习自,李新华老师培训资料。