java多线程为什么要用while而不是if
对于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的更多相关文章
- 40个Java多线程问题总结
前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...
- Java多线程基础知识篇
这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...
- Java多线程 2 线程的生命周期和状态控制
一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...
- 第一章 Java多线程技能
1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...
- java从基础知识(十)java多线程(下)
首先介绍可见性.原子性.有序性.重排序这几个概念 原子性:即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行. 可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到 ...
- Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock
本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...
- Java多线程编程核心技术---学习分享
继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...
- java多线程编程
一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复 ...
- Java多线程开发系列之四:玩转多线程(线程的控制2)
在上节的线程控制(详情点击这里)中,我们讲解了线程的等待join().守护线程.本节我们将会把剩下的线程控制内容一并讲完,主要内容有线程的睡眠.让步.优先级.挂起和恢复.停止等. 废话不多说,我们直接 ...
随机推荐
- MySQL之连接数据库的两种方法
方法一: package DB; import java.sql.Connection; import java.sql.DriverManager; public class Conn { // 定 ...
- CSS之Generator
这个工具可以,收藏一下.CSS Generator
- 零基础Android学习笔记-01 安卓开发环境搭建
安卓开发环境搭建. 1.首先准备JDK,从官网找到JDK下载地址,原来做.NET不熟悉JAVA,干脆用最新的,下载了JDK 1.7的版本.原来装过1.5还要配置环境变量什么的.但1.7好像很给力,装好 ...
- 删除HT和CAS角色与扩展在另一台服务器
背景:原先使用三合一方式部署的架构,如今不再满足企业需求,因此需要将原来的一台服务器多角色的拆分开,即由原来CAS.HT.MBX角色集一台服务器的分成两台服务器来部署,此架构为MBX角色单独部署在 ...
- 利用 Google API 调用谷歌地图 演示1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- XtraGrid使用心得(折叠式主细档、分组统计)
XtraGrid的关键类就是:GridControl和GridView.GridControl本身不显示数据,数据都是显示在GridView/CardView/XXXXView中.GridContro ...
- [Apache Maven Shade Plugin] [example] [001] 官方例子:includes-excludes
链接地址:[Selecting Contents for Uber JAR](http://maven.apache.org/plugins/maven-shade-plugin/examples/i ...
- MATLAB学习(3)
matlab读取图像并转化为灰度图像 image = imread('C:\Users\Administrator\Desktop\图像降噪\src\original image\100.png'); ...
- PHP取二进制文件头快速判断文件类型的实现代码
通过读取文件头信息来识别文件的真实类型. 一般我们都是按照文件扩展名来判断文件类型,但是这个很不靠谱,轻易就通过修改扩展名来躲避了,一般必须要读取文件信息来识别,PHP扩展中提供了类似 exif_im ...
- Delphi XE5教程2:程序组织
内容源自Delphi XE5 UPDATE 2官方帮助<Delphi Reference>,本人水平有限,欢迎各位高人修正相关错误! 也欢迎各位加入到Delphi学习资料汉化中来,有兴趣者 ...