对于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. Part 2 How are the URL's mapped to Controller Action Methods?

    Part 2 How are the URL's mapped to Controller Action Methods? The answer is ASP.NET Routing.Notice t ...

  2. sqlserver关于对列的权限控制

    -- 脚本新建登录数据库的用户 USE [master]GOCREATE LOGIN [sa1] WITH PASSWORD=N'123456', DEFAULT_DATABASE=[master], ...

  3. 将slider滑块从横着变为竖着的时候坐标变换的计算

    ////  ViewController.m//  imageview添加按钮////  Created by hehe on 15/9/22.//  Copyright (c) 2015年 wang ...

  4. java静态代码块、初始化块和构造方法的执行顺序

    分析:当执行new Child()时,它首先去看父类里面有没有静态代码块,如果有,它先去执行父类里面静态代码块里面的内容,当父类的静态代码块里面的内容执行完毕之后,接着去执行子类(自己这个类)里面的静 ...

  5. ios固定高度禁止惯性滚动

    最近测试pad改H5的项目时,固定高度的div,超出部分滚动,但在ios下滑动特别卡顿,安卓上没问题.搜索找到解决办法 固定高度的div设置超出页面滚动,ios会出现卡顿,非常不爽.通过下面css就可 ...

  6. Tomcat找不到service.bat文件

    说明:我们给客户做安装包,Tomcat我们设置了编码和端口,所以用绿色版的,同时又要注册成windows服务.但是bin下面没有service.bat文件(tomcat6.exe,tomcat6x.e ...

  7. windows下 berkerly db的安装配置(修正了关键步骤)

    这个是我从别人的博客上找来的,亲测可用,确实解决了我当时遇到的一些问题. 首先,从http://www.oracle.com/technology/global/cn/software/product ...

  8. 《LDAP服务器和客户端的加密认证》RHEL6——第二篇 运维工程师必考

    服务端的配置: (基于原先配好的ldap服务器)打开加密认证: Iptables –F  setenforce 0 1.编辑ldap的配置文件:slapd.conf 2.启动ldap服务器: 3.切换 ...

  9. Mac系统Finder访问资源库文件夹

    Mac在Lion版本之后,默认隐藏了“资源库”文件夹,如果有时我们又需要访问它,该怎么办呢? 方法一 打开“Finder”,打开“前往”菜单时按住“Option”键. 方法二 我们也可设置Finder ...

  10. [转]高并发访问下避免对象缓存失效引发Dogpile效应

    避免Redis/Memcached缓存失效引发Dogpile效应 Redis/Memcached高并发访问下的缓存失效时可能产生Dogpile效应(Cache Stampede效应). 推荐阅读:高并 ...