Java多线程编程07 -线程通信wait()&notify()&生产者消费者模型

本文详细解析了Java中的线程间通信机制,包括等待/通知机制的使用,以及线程状态的变迁。并通过生产者/消费者模式的实例,展示了如何利用synchronized关键字和wait()/notify()方法实现线程间的高效通信。

线程间通信

-等待/通知机制

轮询的方法实现 线程间通信,简单的例子就是一个线程死循环查询某个条件是不是满足,非常浪费CPU资源。这里讨论等待/通知机制

在Java语言中,实现等待通知机制主要是用:wait()/notify()方法实现。

  • wait()方法 :wait()方法是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“欲执行队列”(阻塞不是就绪)中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait()方法。没有的话,会报出异常。在执行wait()方法后,立刻释放锁。wait(long) 带参数的方法功能:long毫秒内没有被唤醒的话,超出时间自动唤醒。
  • notify()方法:方法notify()也要在同步方法或同步块中调用, 该方法用来通知那些等待的线程,如果有多了线程等待,由线程规划器随机挑选一个wait的线程获得对象锁(唤醒)。注意的是:在执行notify()方法之后,当前线程不会立马释放对象锁,呈wait状态的线程也不能立马获取对象锁,要等到执行notify()的线程将程序执行完(退出同步代码块)之后才可释放锁。
    notifyAll()方法使得正在等待同一资源的所有线程从等待状态退出,进入就绪态,选择优先级最高的线程执行,也有可能是随机,这取决于JVM的具体实现。
-线程状态

Java中的线程的生命周期大体可分为5种状态。

①NEW:这种情况指的是,通过New关键字创建了Thread类(或其子类)的对象(创建)

②RUNNABLE:这种情况指的是Thread类的对象调用了start()方法,这时的线程就等待时间片轮转到自己这,以便获得CPU;第二种情况是线程在处于RUNNING状态时并没有运行完自己的run方法,时间片用完之后回到RUNNABLE状态;还有种情况就是处于BLOCKED状态的线程结束了当前的BLOCKED状态之后重新回到RUNNABLE状态。(就绪)

③RUNNING:这时的线程指的是获得CPU的RUNNABLE线程,RUNNING状态是所有线程都希望获得的状态。(运行)

④DEAD:处于RUNNING状态的线程,在执行完run方法之后,就变成了DEAD状态了。(销毁)

⑤BLOCKED:这种状态指的是处于RUNNING状态的线程,出于某种原因,比如调用了sleep方法、等待用户输入等而让出当前的CPU给其他的线程。(阻塞)

处于RUNNING状态的线程变为BLOCKED状态的原因,除了该线程调用了sleep方法、等待输入原因外,还有就是在当前线程中调用了其他线程的join方法、当访问一个对象的方法时,该方法被锁定等。

相应的,当处于BLocked状态的线程在满足以下条件时就会由该状态转到RUNNABLE状态,这些条件是:sleep的线程醒来(sleep的时间到了)、获得了用户的输入、调用了join的其他线程结束、获得了对象锁。

一般情况下,都是处于RUNNABLE的线程和处于RUNNING状态的线程,互相切换,直到运行完run方法,线程结束,进入DEAD状态。
图片来源自:https://blog.youkuaiyun.com/hangelsing/article/details/44037675

线程状态小节以上内容转载自:
http://blog.youkuaiyun.com/yaolingrui/article/details/7522372
http://zy19982004.iteye.com/blog/1626916

下面总结就绪和阻塞的大体原因:

线程进入Runnable原因大致5种:

  1. sleep的时间到期
  2. 线程调用的阻塞IO已经返回,阻塞方法执行完毕
  3. 线程成功获得同步的监视器(锁)
  4. 线程在等待某个通知,其他线程刚好发出了通知(notify)
  5. 处于挂起状态的线程调用了resume()恢复方法

线程出现阻塞原因大致5种:

  1. 线程调用sleep()方法,主动放弃cpu
  2. 线程调用阻塞IO方法,该方法返回前,线程被阻塞
  3. 线程打算获得同步监视器的其他线程持有(锁)
  4. 线程等待某个通知(wait)
  5. 程序调用suspend()方法将线程挂起。(此方法容易死锁)

- 生产者/消费者模式

一个生产者,一个消费者 生产商品,list是仓库:

public class Test {
	public static void main(String[] args) {			
		try {
			ProducerAndConsumer pc = new ProducerAndConsumer();
			MyThread myThread  =  new MyThread(pc);
			MyThread2 myThread2 =  new MyThread2(pc);
			myThread2.start();
			myThread.start();
			System.out.println("main end!");
		} catch (Exception  e) {
			// TODO: handle exception
			System.out.println("main catch!");
		}
	}
  }

 class MyThread extends Thread {

	private ProducerAndConsumer pc;
	public MyThread(ProducerAndConsumer pc) {
		this.pc = pc;
	}
	@Override
	public void run() { 
		//System.out.println("生产者进程 开始!");
		while (true)
			pc.produce();
	}
  }
 
 class MyThread2 extends Thread {

	private ProducerAndConsumer pc;
	public MyThread2(ProducerAndConsumer pc) {
		this.pc = pc;
	}
	@Override
	public void run() { 
		//System.out.println("消费者进程 开始");
		while (true)
			pc.consume();
	}
 }

 class ProducerAndConsumer{	
	 private List<String> list = new LinkedList<String>();
	
	 synchronized public void produce() {
	
		try {
			//System.out.println("生产开始");
			if (list.size() == 1) {
				this.wait();
			}
			System.out.println("produce !");
		    list.add("考研成功!");
			this.notify();
		} catch (Exception e) {
		}

	 }
	 synchronized public void consume() {

		try {
			//System.out.println("消费开始");
			if (list.size() == 0) {
				this.wait();
			}
			System.out.println("consume !");
			list.remove(0);
			this.notify();
		} catch (Exception e) {
		}
	}
 }

当出现多对一或者-对多或者多对多的时候:(拿一个生产者,多个消费者举例)

public class MyStack { 
   private List list = new ArrayList(); // 缓冲栈,放置生产的产品

   synchronized public void push() { // 放入一个产品
      try {
         if (list.size() == 1) {
            this.wait();
         }
         list.add("anyString=" + Math.random());
         this.notify();
         System.out.println("push=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   synchronized public String pop() { // 消费一个产品
      String returnValue = "";
      try {
         if (list.size() == 0) {
            this.wait();
         }
         returnValue = "" + list.get(0);
         System.out.println("pop操作中的:"
               + Thread.currentThread().getName() + " 线程呈wait状态");
         list.remove(0);
         this.notify();
         System.out.println("pop=" + list.size());
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return returnValue;
   }
}

public class Consumer_Thread extends Thread { // 生产者,消费者线程

   private MyStack myStack;

   public Consumer_Thread(MyStack r) {
      this.myStack = r;
   }

   @Override
   public void run() {
      while (true) {
         myStack.pop();
      }
   }

}

public class Produce_Thread extends Thread {

   private MyStack myStack;

   public Produce_Thread(MyStack p) {
      this.myStack = p;
   }

   @Override
   public void run() {
      while (true) {
         myStack.push();
      }
   }

}

// 主函数
public class Run {
   public static void main(String[] args) throws Exception{
      MyStack myStack = new MyStack();

      Produce_Thread pThread = new Produce_Thread(myStack);
      pThread.start();

      Consumer_Thread cThread1 = new Consumer_Thread(myStack);
      Consumer_Thread cThread2 = new Consumer_Thread(myStack);
      cThread1.start();
      cThread2.start();
   }

}

输出: 
push=1
pop操作中的:Thread-2 线程呈wait状态
pop=0
push=1
pop操作中的:Thread-2 线程呈wait状态
pop=0
Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:653)
	at java.util.ArrayList.get(ArrayList.java:429)
	at entity.MyStack.pop(MyStack.java:28)
	at extthread.Consumer_Thread.run(Consumer_Thread.java:16)

为什么出现索引异常???

原因是在Mystack类中,对缓冲栈的操作时用了if来判断:

if (list.size() == 1) {
    this.wait();
}
if (list.size() == 0) {
    this.wait();
}

比如下面情况: 消费者线程1在等待list.size()=0处等待,切换到生产者1生产一个产品,唤醒另一个消费者2线程,消费者2线程发现list不是空了,就把这个list消费了,并且执行了remove操作之后,调用了this.norify(),此时把消费者线程1唤醒,消费者1接着上次的位置继续执行,它以为list这时候不是空了,就往下remove了,但是实际上上一次生产的产品被消费者2截胡了,自然list是空,删除的时候就索引异常了。所以抛IndexOutOfBoundsException异常。

解决办法:把两个if换成 while,让这个线程被唤醒之后,再重新判断一下list是不是空(因为又锁,所以这个线程被唤醒后,不用担心list内容再变)

但是这种情况下。异常问题解决了,又会迎来假死的情况

因为不能保证你这个nofity唤醒的是异类还是同类。假如生产者一直唤醒生产者,积少成多,大家都在等待,程序就假死了,不能运行下去。解决办法就是:用notifyAll()一股脑唤醒所有,ALLIN!


2019.12.21  科研楼506
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值