对于java多线程的wait()方法,我们在jdk1.6的说明文档里可以看到这样一段话

从上面的截图,我们可以看出,在使用wait方法时,需要使用while循环来判断条件十分满足,而不是if,那么我们思考以下,如果使用if会怎么样?

为方便讲解,我们来看一个被广泛使用的生产消费的例子。代码部分参考  郝斌java视频教程  部分改编。

/*
生产和消费
*/
package multiThread; class SynStack
{
private char[] data = new char[6];
private int cnt = 0; //表示数组有效元素的个数 public synchronized void push(char ch)
{
if (cnt >= data.length)
{
try
{
System.out.println("生产线程"+Thread.currentThread().getName()+"准备休眠");
this.wait();
System.out.println("生产线程"+Thread.currentThread().getName()+"休眠结束了");
}
catch (Exception e)
{
e.printStackTrace();
}
}
this.notify();
data[cnt] = ch;
++cnt;
System.out.printf("生产线程"+Thread.currentThread().getName()+"正在生产第%d个产品,该产品是: %c\n", cnt, ch);
} public synchronized char pop()
{
char ch;
if (cnt <= 0)
{
try
{
System.out.println("消费线程"+Thread.currentThread().getName()+"准备休眠");
this.wait();
System.out.println("消费线程"+Thread.currentThread().getName()+"休眠结束了");
}
catch (Exception e)
{
e.printStackTrace();
}
}
this.notify();
ch = data[cnt-1];
System.out.printf("消费线程"+Thread.currentThread().getName()+"正在消费第%d个产品,该产品是: %c\n", cnt, ch);
--cnt;
return ch;
}
} class Producer implements Runnable
{
private SynStack ss = null;
public Producer(SynStack ss)
{
this.ss = ss;
} public void run()
{
char ch;
for (int i=0; i<10; ++i)
{
// try{
// Thread.sleep(100);
// }
// catch (Exception e){
// } ch = (char)('a'+i);
ss.push(ch);
}
}
} class Consumer implements Runnable
{
private SynStack ss = null; public Consumer(SynStack ss)
{
this.ss = ss;
} public void run()
{
for (int i=0; i<10; ++i)
{
/*try{
Thread.sleep(100);
}
catch (Exception e){
}*/ //System.out.printf("%c\n", ss.pop());
ss.pop();
}
}
} public class TestPC2
{
public static void main(String[] args)
{
SynStack ss = new SynStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss); Thread t1 = new Thread(p);
t1.setName("1号");
t1.start();
/*Thread t2 = new Thread(p);
t2.setName("2号");
t2.start();*/ Thread t6 = new Thread(c);
t6.setName("6号");
t6.start();
/*Thread t7 = new Thread(c);
t7.setName("7号");
t7.start();*/
}
}

  上面的代码只有一个消费者线程和一个生产者线程,程序运行完美,没有任何错误,那为为什么jdk里面强调要用while呢?

这个问题,我之前也向了很久,同事提到了一点,这个程序如果用到多个生产者和消费者的情况,就会出错,我试了一下,确实会出错。但是我不能明白为什么就会出错。

不是有synchronized关键字加锁了吗?

其实,这里也错在我对wait方法原理的实际运行效果不是很了解,当我在wait方法的前后都加上输出提示语句后,我明白了。

一个线程执行了wait方法以后,它不会再继续执行了,直到被notify唤醒。

那么唤醒以后从何处开始执行?

这是解决这里出错原因的关键。

我们尝试修改代码,实现一个生产线程,两个消费线程。

/*
生产和消费
*/
package multiThread; class SynStack
{
private char[] data = new char[6];
private int cnt = 0; //表示数组有效元素的个数 public synchronized void push(char ch)
{
if (cnt >= data.length)
{
try
{
System.out.println("生产线程"+Thread.currentThread().getName()+"准备休眠");
this.wait();
System.out.println("生产线程"+Thread.currentThread().getName()+"休眠结束了");
}
catch (Exception e)
{
e.printStackTrace();
}
}
this.notify();
data[cnt] = ch;
++cnt;
System.out.printf("生产线程"+Thread.currentThread().getName()+"正在生产第%d个产品,该产品是: %c\n", cnt, ch);
} public synchronized char pop()
{
char ch;
if (cnt <= 0)
{
try
{
System.out.println("消费线程"+Thread.currentThread().getName()+"准备休眠");
this.wait();
System.out.println("消费线程"+Thread.currentThread().getName()+"休眠结束了");
}
catch (Exception e)
{
e.printStackTrace();
}
}
this.notify();
ch = data[cnt-1];
System.out.printf("消费线程"+Thread.currentThread().getName()+"正在消费第%d个产品,该产品是: %c\n", cnt, ch);
--cnt;
return ch;
}
} class Producer implements Runnable
{
private SynStack ss = null;
public Producer(SynStack ss)
{
this.ss = ss;
} public void run()
{
char ch;
for (int i=0; i<10; ++i)
{
// try{
// Thread.sleep(100);
// }
// catch (Exception e){
// } ch = (char)('a'+i);
ss.push(ch);
}
}
} class Consumer implements Runnable
{
private SynStack ss = null; public Consumer(SynStack ss)
{
this.ss = ss;
} public void run()
{
for (int i=0; i<10; ++i)
{
/*try{
Thread.sleep(100);
}
catch (Exception e){
}*/ //System.out.printf("%c\n", ss.pop());
ss.pop();
}
}
} public class TestPC2
{
public static void main(String[] args)
{
SynStack ss = new SynStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss); Thread t1 = new Thread(p);
t1.setName("1号");
t1.start();
/*Thread t2 = new Thread(p);
t2.setName("2号");
t2.start();*/ Thread t6 = new Thread(c);
t6.setName("6号");
t6.start();
Thread t7 = new Thread(c);
t7.setName("7号");
t7.start();
}
}

上面代码就是在main函数里增加了一个消费线程。

然后错误出现了。

数组越界,为什么会这样?

问题的关键就在于7号消费线程唤醒了6号消费线程,而6号消费线程被唤醒以后,它从哪里开始执行是关键!!!!

它会执行

System.out.println("消费线程"+Thread.currentThread().getName()+"休眠结束了");

这行代码。

不是从pop()方法的开始处执行。

那么这跟使用if方法有什么关系?

因为,7号线程唤醒了6号线程,并执行了以下4行代码。

		ch = data[cnt-1];
System.out.printf("消费线程"+Thread.currentThread().getName()+"正在消费第%d个产品,该产品是: %c\n", cnt, ch);
--cnt;
return ch;

7号线程执行完上面的代码后,cnt就=0了

  又因为6号线程被唤醒时已经处在if方法体内,它不会再去执行if条件判断,所以就顺序往下执行,这个时候执行

ch = data[cnt-1];
就会出现越界异常。
假如使用while就不会,因为当唤醒了6号线程以后,它依然会去执行循环条件检测。所以不可能执行下去,保证了程序的安全。

java多线程为什么要用while而不是if的更多相关文章

  1. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  2. Java多线程基础知识篇

    这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...

  3. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  4. 第一章 Java多线程技能

    1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...

  5. java从基础知识(十)java多线程(下)

    首先介绍可见性.原子性.有序性.重排序这几个概念 原子性:即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行. 可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到 ...

  6. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

    本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...

  7. Java多线程编程核心技术---学习分享

    继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...

  8. java多线程编程

    一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复 ...

  9. Java多线程开发系列之四:玩转多线程(线程的控制2)

    在上节的线程控制(详情点击这里)中,我们讲解了线程的等待join().守护线程.本节我们将会把剩下的线程控制内容一并讲完,主要内容有线程的睡眠.让步.优先级.挂起和恢复.停止等. 废话不多说,我们直接 ...

随机推荐

  1. 五、PackageManager获取版本号

    PackageInfo代表的是关于一个包的所有信息,就相当于一个APP应用的清单文件中收集到的所有信息. 通过这个类我们就可以获取类似版本号等一些信息. 1 2 3 4 5 6 7 8 9 10 11 ...

  2. CSS之text-stroke

    啧啧啧( ̄︶ ̄),国庆人太多,所以假期还没结束就提前几天回来了.今天也是挤火车赶回来的,被夹在门里好尴尬啊~~ 回家的这几天在外婆家招待过的好爽啊,又是鱼又是肉,馋的我都不想走了. 然而自己在家只能“ ...

  3. 收藏的js学习小例子

    1.js模拟java里的Map function Map(){ var obj = {} ; this.put = function(key , value){ obj[key] = value ; ...

  4. windows server 2008 防火墙配置

    防火墙的配置主要是过滤用户是否能够访问服务器,哪些用户能够访问,哪些用户不能访问.类似于交换机上的acl(访问控制列表) 在windows服务器上有入站规则以及出站规则,那我们首先得了解一下什么是入站 ...

  5. 检测是否支持HTML5中的Video标签

    //检测是否支持HTML5 function checkVideo() { if (!!document.createElement('video').canPlayType) { var vidTe ...

  6. in/exists not in/not exists null

    in/not in exists/not exists null的理解 两个测试表 create table tmp01 as with tmp as ( select '1' as id from ...

  7. 遇到的 autoresizingMask 相关的问题

    1.前言 当一个控件设置好 frame,然后出现会 frame 显示不准或是跟随父控件的变化而变化了,你就要考虑是否是 autoresizing 的问题了 当在 xib 中布局时,报 NSAutore ...

  8. AMQ学习笔记 - 11. Spring-JmsTemplate之执行

    概述 前面我们分别介绍了发送.接收和浏览,这三个的实现都依赖于将要介绍的执行. 执行算是一个相对比较底层的方法系列,一般情况下,我们不需要直接面向将要介绍的方法. 执行 1.关于回调接口 在讲执行之前 ...

  9. 巧用Excel分列功能处理数据

    Technorati 标签: 数据处理      今天,主要工作就是处理测试数据,统计汇总成图表来显示.先来说说要求,然后给出我在折腾这堆数据中遇到的问题以及解决方法.   问题要求:       格 ...

  10. 使用DriverManager获取数据库连接的一个小改进

    由于使用DriverManager获取数据库连接时,由于DriverManager实现类中有一段静态代码块,可以直接注册驱动,且可以同时管理多个驱动程序 所以当换数据库连接时需要指定不同的数据库,那么 ...