ava
中
yield(),sleep()
以及
wait()
的区
别
往往混淆了
这
三个函数的使用。
从操作系
统
的角度
讲
,
os
会
维护
一个
ready queue
(就
绪
的
线
程
队
列)。并且在某一
时
刻
cpu
只
为
ready queue
中位于
队
列
头
部的
线
程服
务
。
但是当前正在被服 务 的 线 程可能 觉 得 cpu 的服 务质 量不 够 好,于是提前退出, 这 就是 yield 。
或者当前正在被服 务 的 线 程需要睡一会,醒来后 继续 被服 务 , 这 就是 sleep 。
但是当前正在被服 务 的 线 程可能 觉 得 cpu 的服 务质 量不 够 好,于是提前退出, 这 就是 yield 。
或者当前正在被服 务 的 线 程需要睡一会,醒来后 继续 被服 务 , 这 就是 sleep 。
sleep
方法不推荐使用,可用
wait
。
线 程退出最好自己 实现 ,在运行状 态 中一直 检验 一个状 态 ,如果 这 个状 态为 真,就一直 运行,如果外界更改了 这 个状 态变 量,那 么线 程就停止运行。
线 程退出最好自己 实现 ,在运行状 态 中一直 检验 一个状 态 ,如果 这 个状 态为 真,就一直 运行,如果外界更改了 这 个状 态变 量,那 么线 程就停止运行。
sleep()
使当前
线
程
进
入停滞状
态
,所以
执
行
sleep()
的
线
程在指定的
时间
内肯定不会
执
行;
yield()
只是使当前
线
程重新回到可
执
行状
态
,所以
执
行
yield()
的
线
程有可能在
进
入到可
执
行状
态
后
马
上又被
执
行。
sleep() 可使 优 先 级 低的 线 程得到 执 行的机会,当然也可以 让 同 优 先 级 和高 优 先 级 的 线 程有 执 行的机会; yield() 只能使同 优 先 级 的 线 程有 执 行的机会。
sleep() 可使 优 先 级 低的 线 程得到 执 行的机会,当然也可以 让 同 优 先 级 和高 优 先 级 的 线 程有 执 行的机会; yield() 只能使同 优 先 级 的 线 程有 执 行的机会。
当
调
用
wait()
后,
线
程会
释
放掉它所占有的
“
锁标
志
”
,从而使
线
程所在
对
象中的其它
synchronized
数据可被
别
的
线
程使用。
waite()
和
notify()
因
为
会
对对
象的
“
锁标
志
”
进
行操作,所以它
们
必
须
在
synchronized
函数或
synchronized
block
中
进
行
调
用。如果在
non-synchronized
函数或
non-synchronized
block
中
进
行
调
用,
虽
然能
编译
通
过
,但在运行
时
会
发
生
IllegalMonitorStateException
的异常。
彻
底明白多
线
程通信机制:
线
程
间
的通信
1. 线 程的几 种 状 态
线 程有四 种 状 态 ,任何一个 线 程肯定 处 于 这 四 种 状 态 中的一 种 :
1) 产 生( New ): 线 程 对 象已 经产 生,但尚未被启 动 ,所以无法 执 行。如通 过 new 产 生了一个 线 程 对 象后没 对 它 调 用 start() 函数之前。
2) 可 执 行( Runnable ): 每 个支持多 线 程的系 统 都有一个排程器,排程器会从 线 程池中 选择 一个 线 程并启 动 它。当一个 线 程 处 于可 执 行状 态时 ,表示它可能正 处 于 线 程池中等待排排程器启 动 它;也可能它已正在 执 行。如 执 行了一个 线 程 对 象的 start() 方法后, 线 程就 处 于可 执 行状 态 ,但 显 而易 见 的是此 时线 程不一定正在 执 行中。
3) 死亡( Dead ):当一个 线 程 正常 结 束,它便 处 于死亡状 态 。如一个 线 程的 run() 函数 执 行完 毕 后 线 程就 进 入死亡状 态 。
4) 停滞( Blocked ):当一个 线 程 处 于停滞状 态时 ,系 统 排程器就会忽略它,不 对 它 进 行排程。当 处 于停滞状 态 的 线 程重新回到可 执 行状 态时 ,它有可能重新 执 行。如通 过对 一个 线 程 调 用 wait() 函数后, 线 程就 进 入停滞状 态 ,只有当两次 对该线 程 调 用 notify 或 notifyAll 后它才能两次回到可 执 行状 态 。
2. class Thread 下的常用函数函数
2.1 suspend() 、 resume()
1) 通 过 suspend() 函数,可使 线 程 进 入停滞状 态 。通 过 suspend() 使 线 程 进 入停滞状 态 后,除非收到 resume() 消息,否 则该线 程不会 变 回可 执 行状 态 。
2) 当 调 用 suspend() 函数后, 线 程不会 释 放它的 “ 锁标 志 ” 。
例 11 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<5; i++){
shareVar++;
if(shareVar==5){
this.suspend(); // ( 1 )
}
}
}
else{
System.out.print(Thread.currentThread().getName());
System.out.println(" shareVar = " + shareVar);
this.resume(); // ( 2 )
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); // ( 5 )
//t1.start(); // ( 3 )
t2.start(); // ( 4 )
}
}
运行 结 果 为 :
t2 shareVar = 5
i. 当代 码 ( 5 )的 t1 所 产 生的 线 程运行到代 码 ( 1 ) 处时 , 该线 程 进 入停滞状 态 。然后排程器从 线 程池中 唤 起代 码 ( 4 )的 t2 所 产 生的 线 程,此 时 shareVar 值 不 为 0 ,所以 执 行 else 中的 语 句。
ii. 也 许 你会 问 ,那 执 行代 码 ( 2 )后 为 什 么 不会使 t1 进 入可 执 行状 态 呢?正如前面所 说 , t1 和 t2 是两个不同 对 象的 线 程,而代 码 ( 1 )和( 2 )都只 对 当前 对 象 进 行操作,所以 t1 所 产 生的 线 程 执 行代 码 ( 1 )的 结 果是 对 象 t1 的当前 线 程 进 入停滞状 态 ;而 t2 所 产 生的 线 程 执 行代 码 ( 2 )的 结 果是把 对 象 t2 中的所有 处 于停滞状 态 的 线 程 调 回到可 执 行状 态 。
iii. 那 现 在把代 码 ( 4 )注 释 掉,并去掉代 码 ( 3 )的注 释 ,是不是就能使 t1 重新回到可 执 行状 态 呢?运行 结 果是什 么 也不 输 出。 为 什 么 会 这样 呢?也 许 你会 认为 ,当代 码 ( 5 )所 产 生的 线 程 执 行到代 码 ( 1 ) 时 ,它 进 入停滞状 态 ;而代 码 ( 3 )所 产 生的 线 程和代 码 ( 5 )所 产 生的 线 程是属于同一个 对 象的,那 么 就当代 码 ( 3 )所 产 生的 线 程 执 行到代 码 ( 2 ) 时 ,就可使代 码 ( 5 )所 产 生的 线 程 执 行回到可 执 行 状 态 。但是要清楚, suspend() 函数只是 让 当前 线 程 进 入停滞状 态 ,但并不 释 放当前 线 程所 获 得的 “ 锁标 志 ” 。所以当代 码 ( 5 )所 产 生的 线 程 进 入停滞状 态时 ,代 码 ( 3 )所 产 生的 线 程仍不能启 动 ,因 为 当前 对 象的 “ 锁标 志 ” 仍被代 码 ( 5 )所 产 生的 线 程占有。
2.2 sleep()
1) sleep () 函数有一个参数,通 过 参数可使 线 程在指定的 时间 内 进 入停滞状 态 ,当指定的 时间过 后, 线 程 则 自 动进 入可 执 行状 态 。
2) 当 调 用 sleep () 函数后, 线 程不会 释 放它的 “ 锁标 志 ” 。
例 12 :
class TestThreadMethod extends Thread{
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<3; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
try{
Thread.sleep(100); // ( 4 )
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); ( 1 )
t1.start(); ( 2 )
//t2.start(); ( 3 )
}
}
运行 结 果 为 :
t1 : 0
t1 : 1
t1 : 2
t1 : 0
t1 : 1
t1 : 2
由 结 果可 证 明, 虽 然在 run() 中 执 行了 sleep() ,但是它不会 释 放 对 象的 “ 锁标 志 ” ,所以除非代 码 (1) 的 线 程 执 行完 run() 函数并 释 放 对 象的 “ 锁标 志 ” ,否 则 代 码 ( 2 )的 线 程永 远 不会 执 行。
如果把代 码 ( 2 )注 释 掉,并去掉代 码 ( 3 )的注 释 , 结 果将 变为 :
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
由于 t1 和 t2 是两个 对 象的 线 程,所以当 线 程 t1 通 过 sleep() 进 入停滞 时 ,排程器会从 线 程池中 调 用其它的可 执 行 线 程,从而 t2 线 程被启 动 。
例 13 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<5; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
try{
if(Thread.currentThread().getName().equals("t1"))
Thread.sleep(200);
else
Thread.sleep(100);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start();
//t1.start();
t2.start();
}
}
运行 结 果 为 :
t1 : 0
t2 : 0
t2 : 1
t1 : 1
t2 : 2
t2 : 3
t1 : 2
t2 : 4
t1 : 3
t1 : 4
由于 线 程 t1 调 用了 sleep(200) ,而 线 程 t2 调 用了 sleep(100) ,所以 线 程 t2 处 于停滞状 态 的 时间 是 线 程 t1 的一半,从从 结 果反映出来的就是 线 程 t2 打印两倍次 线 程 t1 才打印一次。
2.3 yield()
1) 通 过 yield () 函数,可使 线 程 进 入可 执 行状 态 ,排程器从可 执 行状 态 的 线 程中重新 进 行排程。所以 调 用了 yield() 的函数也有可能 马 上被 执 行。
2) 当 调 用 yield () 函数后, 线 程不会 释 放它的 “ 锁标 志 ” 。
例 14 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<4; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
Thread.yield();
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start();
t1.start(); // ( 1 )
//t2.start(); ( 2 )
}
}
运行 结 果 为 :
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 0
t1 : 1
t1 : 2
t1 : 3
从 结 果可知 调 用 yield() 时 并不会 释 放 对 象的 “ 锁标 志 ” 。
如果把代 码 ( 1 )注 释 掉,并去掉代 码 ( 2 )的注 释 , 结 果 为 :
t1 : 0
t1 : 1
t2 : 0
t1 : 2
t2 : 1
t1 : 3
t2 : 2
t2 : 3
从 结 果可知, 虽 然 t1 线 程 调 用了 yield() ,但它 马 上又被 执 行了。
2.4 sleep() 和 yield() 的区 别
1) sleep() 使当前 线 程 进 入停滞状 态 ,所以 执 行 sleep() 的 线 程在指定的 时间 内肯定不 会 执 行; yield() 只是使当前 线 程重新回到可 执 行状 态 ,所以 执 行 yield() 的 线 程有可能在 进 入到可 执 行状 态 后 马 上又被 执 行。
2) sleep() 可使 优 先 级 低的 线 程得到 执 行的机会,当然也可以 让 同 优 先 级 和高 优 先 级 的 线 程有 执 行的机会; yield() 只能使同 优 先 级 的 线 程有 执 行的机会。
例 15 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public void run(){
for(int i=0; i<4; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
//Thread.yield(); ( 1 )
/* ( 2 ) */
try{
Thread.sleep(3000);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
运行 结 果 为 :
t1 : 0
t1 : 1
t2 : 0
t1 : 2
t2 : 1
t1 : 3
t2 : 2
t2 : 3
由 结 果可 见 ,通 过 sleep() 可使 优 先 级较 低的 线 程有 执 行的机会。 注 释 掉代 码 ( 2 ),并去掉代 码 ( 1 )的注 释 , 结 果 为 :
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t2 : 0
t2 : 1
t2 : 2
t2 : 3
可 见 , 调 用 yield() ,不同 优 先 级 的 线 程永 远 不会得到 执 行机会。
2.5 join()
使 调 用 join() 的 线 程 执 行完 毕 后才能 执 行其它 线 程,在一定意 义 上,它可以 实现 同 步 的功能。
例 16 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public void run(){
for(int i=0; i<4; i++){
System.out.println(" " + i);
try{
Thread.sleep(3000);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
t1.start();
try{
t1.join();
}
catch(InterruptedException e){}
t1.start();
}
}
运行 结 果 为 :
0
1
2
3
0
1
2
3
3. class Object 下常用的 线 程函数
wait() 、 notify() 和 notifyAll() 这 三个函数由 java.lang.Object 类 提供,用于 协调 多个 线 程 对 共享数据的存取。
3.1 wait() 、 notify() 和 notifyAll()
1) wait() 函数有两 种 形式:第一 种 形式接受一个毫秒 值 ,用于在指定 时间长 度内 暂 停 线 程,使 线 程 进 入停滞状 态 。第二 种 形式 为 不 带 参数,代表 waite() 在 notify() 或 notifyAll() 之前会持 续 停滞。
2) 当 对 一个 对 象 执 行 notify() 时 ,会从 线 程等待池中移走 该 任意一个 线 程,并把它放到 锁标 志等待池中;当 对 一个 对 象 执 行 notifyAll() 时 ,会从 线 程等待池中移走所有 该对 象的所有 线 程,并把它 们 放到 锁标 志等待池中。
3) 当 调 用 wait() 后, 线 程会 释 放掉它所占有的 “ 锁标 志 ” ,从而使 线 程所在 对 象中的其它 synchronized 数据可被 别 的 线 程使用。
例 17 :
下面,我 们 将 对 例 11 中的例子 进 行修改
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<10; i++){
shareVar++;
if(shareVar==5){
try{
this.wait(); // ( 4 )
}
catch(InterruptedException e){}
}
}
}
if(shareVar!=0){
System.out.print(Thread.currentThread().getName());
System.out.println(" shareVar = " + shareVar);
this.notify(); // ( 5 )
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); // ( 1 )
//t1.start(); ( 2 )
t2.start(); // ( 3 )
}
}
运行 结 果 为 :
t2 shareVar = 5
因 为 t1 和 t2 是两个不同 对 象,所以 线 程 t2 调 用代 码 ( 5 )不能 唤 起 线 程 t1 。 如果去掉代 码 ( 2 )的注 释 ,并注 释 掉代 码 ( 3 ), 结 果 为 :
t1 shareVar = 5
t1 shareVar = 10
这 是因 为 ,当代 码 ( 1 )的 线 程 执 行到代 码 ( 4 ) 时 ,它 进 入停滞状 态 ,并 释 放 对 象的 锁 状 态 。接着,代 码 ( 2 )的 线 程 执 行 run() ,由于此 时 shareVar 值为 5 ,所以 执 行打印 语 句并 调 用代 码 ( 5 )使代 码 ( 1 )的 线 程 进 入可 执 行状 态 ,然后代 码 ( 2 )的 线 程 结 束。当代 码 ( 1 )的 线 程重新 执 行后,它接着 执 行 for() 循 环 一直到 shareVar=10 ,然后打印 shareVar 。
3.2 wait() 、 notify() 和 synchronized
waite() 和 notify() 因 为 会 对对 象的 “ 锁标 志 ” 进 行操作,所以它 们 必 须 在 synchronized 函数或 synchronized block 中 进 行 调 用。如果在 non-synchronized 函数或 non-synchronized block 中 进 行 调 用, 虽 然能 编译 通 过 ,但在运行 时 会 发 生 IllegalMonitorStateException 的异常。
例 18 :
class TestThreadMethod extends Thread{
public int shareVar = 0;
public TestThreadMethod(String name){
super(name);
new Notifier(this);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<5; i++){
shareVar++;
System.out.println("i = " + shareVar);
try{
System.out.println("wait......");
this.wait();
}
catch(InterruptedException e){}
}
}
}
}
class Notifier extends Thread{
private TestThreadMethod ttm;
Notifier(TestThreadMethod t){
ttm = t;
start();
}
public void run(){
while(true){
try{
sleep(2000);
}
catch(InterruptedException e){}
/*1 要同 步 的不是当前 对 象的做法 */
synchronized(ttm){
System.out.println("notify......");
ttm.notify();
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
t1.start();
}
}
运行 结 果 为 :
i = 1
wait......
notify......
i = 2
wait......
notify......
i = 3
wait......
notify......
i = 4
wait......
notify......
i = 5
wait......
notify......
4. wait() 、 notify() 、 notifyAll() 和 suspend() 、 resume() 、 sleep() 的 讨论
4.1 这 两 组 函数 的区 别
1) wait() 使当前 线 程 进 入停滞状 态时 , 还 会 释 放当前 线 程所占有的 “ 锁标 志 ” ,从而使 线 程 对 象中的 synchronized 资 源可被 对 象中 别 的 线 程使用;而 suspend() 和 sleep() 使当前 线 程 进 入停滞状 态时 不会 释 放当前 线 程所占有的 “ 锁标 志 ” 。
2) 前一 组 函数必 须 在 synchronized 函数或 synchronized block 中 调 用,否 则 在运行 时 会 产 生 错误 ;而后一 组 函数可以 non-synchronized 函数和 synchronized block 中 调 用。
4.2 这 两 组 函数的取舍
Java2 已不建 议 使用后一 组 函数。因 为 在 调 用 wait() 时 不会 释 放当前 线 程所取得的 “ 锁标 志 ” , 这样 很容易造成 “ 死 锁 ” 。
1. 线 程的几 种 状 态
线 程有四 种 状 态 ,任何一个 线 程肯定 处 于 这 四 种 状 态 中的一 种 :
1) 产 生( New ): 线 程 对 象已 经产 生,但尚未被启 动 ,所以无法 执 行。如通 过 new 产 生了一个 线 程 对 象后没 对 它 调 用 start() 函数之前。
2) 可 执 行( Runnable ): 每 个支持多 线 程的系 统 都有一个排程器,排程器会从 线 程池中 选择 一个 线 程并启 动 它。当一个 线 程 处 于可 执 行状 态时 ,表示它可能正 处 于 线 程池中等待排排程器启 动 它;也可能它已正在 执 行。如 执 行了一个 线 程 对 象的 start() 方法后, 线 程就 处 于可 执 行状 态 ,但 显 而易 见 的是此 时线 程不一定正在 执 行中。
3) 死亡( Dead ):当一个 线 程 正常 结 束,它便 处 于死亡状 态 。如一个 线 程的 run() 函数 执 行完 毕 后 线 程就 进 入死亡状 态 。
4) 停滞( Blocked ):当一个 线 程 处 于停滞状 态时 ,系 统 排程器就会忽略它,不 对 它 进 行排程。当 处 于停滞状 态 的 线 程重新回到可 执 行状 态时 ,它有可能重新 执 行。如通 过对 一个 线 程 调 用 wait() 函数后, 线 程就 进 入停滞状 态 ,只有当两次 对该线 程 调 用 notify 或 notifyAll 后它才能两次回到可 执 行状 态 。
2. class Thread 下的常用函数函数
2.1 suspend() 、 resume()
1) 通 过 suspend() 函数,可使 线 程 进 入停滞状 态 。通 过 suspend() 使 线 程 进 入停滞状 态 后,除非收到 resume() 消息,否 则该线 程不会 变 回可 执 行状 态 。
2) 当 调 用 suspend() 函数后, 线 程不会 释 放它的 “ 锁标 志 ” 。
例 11 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<5; i++){
shareVar++;
if(shareVar==5){
this.suspend(); // ( 1 )
}
}
}
else{
System.out.print(Thread.currentThread().getName());
System.out.println(" shareVar = " + shareVar);
this.resume(); // ( 2 )
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); // ( 5 )
//t1.start(); // ( 3 )
t2.start(); // ( 4 )
}
}
运行 结 果 为 :
t2 shareVar = 5
i. 当代 码 ( 5 )的 t1 所 产 生的 线 程运行到代 码 ( 1 ) 处时 , 该线 程 进 入停滞状 态 。然后排程器从 线 程池中 唤 起代 码 ( 4 )的 t2 所 产 生的 线 程,此 时 shareVar 值 不 为 0 ,所以 执 行 else 中的 语 句。
ii. 也 许 你会 问 ,那 执 行代 码 ( 2 )后 为 什 么 不会使 t1 进 入可 执 行状 态 呢?正如前面所 说 , t1 和 t2 是两个不同 对 象的 线 程,而代 码 ( 1 )和( 2 )都只 对 当前 对 象 进 行操作,所以 t1 所 产 生的 线 程 执 行代 码 ( 1 )的 结 果是 对 象 t1 的当前 线 程 进 入停滞状 态 ;而 t2 所 产 生的 线 程 执 行代 码 ( 2 )的 结 果是把 对 象 t2 中的所有 处 于停滞状 态 的 线 程 调 回到可 执 行状 态 。
iii. 那 现 在把代 码 ( 4 )注 释 掉,并去掉代 码 ( 3 )的注 释 ,是不是就能使 t1 重新回到可 执 行状 态 呢?运行 结 果是什 么 也不 输 出。 为 什 么 会 这样 呢?也 许 你会 认为 ,当代 码 ( 5 )所 产 生的 线 程 执 行到代 码 ( 1 ) 时 ,它 进 入停滞状 态 ;而代 码 ( 3 )所 产 生的 线 程和代 码 ( 5 )所 产 生的 线 程是属于同一个 对 象的,那 么 就当代 码 ( 3 )所 产 生的 线 程 执 行到代 码 ( 2 ) 时 ,就可使代 码 ( 5 )所 产 生的 线 程 执 行回到可 执 行 状 态 。但是要清楚, suspend() 函数只是 让 当前 线 程 进 入停滞状 态 ,但并不 释 放当前 线 程所 获 得的 “ 锁标 志 ” 。所以当代 码 ( 5 )所 产 生的 线 程 进 入停滞状 态时 ,代 码 ( 3 )所 产 生的 线 程仍不能启 动 ,因 为 当前 对 象的 “ 锁标 志 ” 仍被代 码 ( 5 )所 产 生的 线 程占有。
2.2 sleep()
1) sleep () 函数有一个参数,通 过 参数可使 线 程在指定的 时间 内 进 入停滞状 态 ,当指定的 时间过 后, 线 程 则 自 动进 入可 执 行状 态 。
2) 当 调 用 sleep () 函数后, 线 程不会 释 放它的 “ 锁标 志 ” 。
例 12 :
class TestThreadMethod extends Thread{
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<3; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
try{
Thread.sleep(100); // ( 4 )
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); ( 1 )
t1.start(); ( 2 )
//t2.start(); ( 3 )
}
}
运行 结 果 为 :
t1 : 0
t1 : 1
t1 : 2
t1 : 0
t1 : 1
t1 : 2
由 结 果可 证 明, 虽 然在 run() 中 执 行了 sleep() ,但是它不会 释 放 对 象的 “ 锁标 志 ” ,所以除非代 码 (1) 的 线 程 执 行完 run() 函数并 释 放 对 象的 “ 锁标 志 ” ,否 则 代 码 ( 2 )的 线 程永 远 不会 执 行。
如果把代 码 ( 2 )注 释 掉,并去掉代 码 ( 3 )的注 释 , 结 果将 变为 :
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
由于 t1 和 t2 是两个 对 象的 线 程,所以当 线 程 t1 通 过 sleep() 进 入停滞 时 ,排程器会从 线 程池中 调 用其它的可 执 行 线 程,从而 t2 线 程被启 动 。
例 13 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<5; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
try{
if(Thread.currentThread().getName().equals("t1"))
Thread.sleep(200);
else
Thread.sleep(100);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start();
//t1.start();
t2.start();
}
}
运行 结 果 为 :
t1 : 0
t2 : 0
t2 : 1
t1 : 1
t2 : 2
t2 : 3
t1 : 2
t2 : 4
t1 : 3
t1 : 4
由于 线 程 t1 调 用了 sleep(200) ,而 线 程 t2 调 用了 sleep(100) ,所以 线 程 t2 处 于停滞状 态 的 时间 是 线 程 t1 的一半,从从 结 果反映出来的就是 线 程 t2 打印两倍次 线 程 t1 才打印一次。
2.3 yield()
1) 通 过 yield () 函数,可使 线 程 进 入可 执 行状 态 ,排程器从可 执 行状 态 的 线 程中重新 进 行排程。所以 调 用了 yield() 的函数也有可能 马 上被 执 行。
2) 当 调 用 yield () 函数后, 线 程不会 释 放它的 “ 锁标 志 ” 。
例 14 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<4; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
Thread.yield();
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start();
t1.start(); // ( 1 )
//t2.start(); ( 2 )
}
}
运行 结 果 为 :
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 0
t1 : 1
t1 : 2
t1 : 3
从 结 果可知 调 用 yield() 时 并不会 释 放 对 象的 “ 锁标 志 ” 。
如果把代 码 ( 1 )注 释 掉,并去掉代 码 ( 2 )的注 释 , 结 果 为 :
t1 : 0
t1 : 1
t2 : 0
t1 : 2
t2 : 1
t1 : 3
t2 : 2
t2 : 3
从 结 果可知, 虽 然 t1 线 程 调 用了 yield() ,但它 马 上又被 执 行了。
2.4 sleep() 和 yield() 的区 别
1) sleep() 使当前 线 程 进 入停滞状 态 ,所以 执 行 sleep() 的 线 程在指定的 时间 内肯定不 会 执 行; yield() 只是使当前 线 程重新回到可 执 行状 态 ,所以 执 行 yield() 的 线 程有可能在 进 入到可 执 行状 态 后 马 上又被 执 行。
2) sleep() 可使 优 先 级 低的 线 程得到 执 行的机会,当然也可以 让 同 优 先 级 和高 优 先 级 的 线 程有 执 行的机会; yield() 只能使同 优 先 级 的 线 程有 执 行的机会。
例 15 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public void run(){
for(int i=0; i<4; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
//Thread.yield(); ( 1 )
/* ( 2 ) */
try{
Thread.sleep(3000);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
运行 结 果 为 :
t1 : 0
t1 : 1
t2 : 0
t1 : 2
t2 : 1
t1 : 3
t2 : 2
t2 : 3
由 结 果可 见 ,通 过 sleep() 可使 优 先 级较 低的 线 程有 执 行的机会。 注 释 掉代 码 ( 2 ),并去掉代 码 ( 1 )的注 释 , 结 果 为 :
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t2 : 0
t2 : 1
t2 : 2
t2 : 3
可 见 , 调 用 yield() ,不同 优 先 级 的 线 程永 远 不会得到 执 行机会。
2.5 join()
使 调 用 join() 的 线 程 执 行完 毕 后才能 执 行其它 线 程,在一定意 义 上,它可以 实现 同 步 的功能。
例 16 :
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public void run(){
for(int i=0; i<4; i++){
System.out.println(" " + i);
try{
Thread.sleep(3000);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
t1.start();
try{
t1.join();
}
catch(InterruptedException e){}
t1.start();
}
}
运行 结 果 为 :
0
1
2
3
0
1
2
3
3. class Object 下常用的 线 程函数
wait() 、 notify() 和 notifyAll() 这 三个函数由 java.lang.Object 类 提供,用于 协调 多个 线 程 对 共享数据的存取。
3.1 wait() 、 notify() 和 notifyAll()
1) wait() 函数有两 种 形式:第一 种 形式接受一个毫秒 值 ,用于在指定 时间长 度内 暂 停 线 程,使 线 程 进 入停滞状 态 。第二 种 形式 为 不 带 参数,代表 waite() 在 notify() 或 notifyAll() 之前会持 续 停滞。
2) 当 对 一个 对 象 执 行 notify() 时 ,会从 线 程等待池中移走 该 任意一个 线 程,并把它放到 锁标 志等待池中;当 对 一个 对 象 执 行 notifyAll() 时 ,会从 线 程等待池中移走所有 该对 象的所有 线 程,并把它 们 放到 锁标 志等待池中。
3) 当 调 用 wait() 后, 线 程会 释 放掉它所占有的 “ 锁标 志 ” ,从而使 线 程所在 对 象中的其它 synchronized 数据可被 别 的 线 程使用。
例 17 :
下面,我 们 将 对 例 11 中的例子 进 行修改
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<10; i++){
shareVar++;
if(shareVar==5){
try{
this.wait(); // ( 4 )
}
catch(InterruptedException e){}
}
}
}
if(shareVar!=0){
System.out.print(Thread.currentThread().getName());
System.out.println(" shareVar = " + shareVar);
this.notify(); // ( 5 )
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); // ( 1 )
//t1.start(); ( 2 )
t2.start(); // ( 3 )
}
}
运行 结 果 为 :
t2 shareVar = 5
因 为 t1 和 t2 是两个不同 对 象,所以 线 程 t2 调 用代 码 ( 5 )不能 唤 起 线 程 t1 。 如果去掉代 码 ( 2 )的注 释 ,并注 释 掉代 码 ( 3 ), 结 果 为 :
t1 shareVar = 5
t1 shareVar = 10
这 是因 为 ,当代 码 ( 1 )的 线 程 执 行到代 码 ( 4 ) 时 ,它 进 入停滞状 态 ,并 释 放 对 象的 锁 状 态 。接着,代 码 ( 2 )的 线 程 执 行 run() ,由于此 时 shareVar 值为 5 ,所以 执 行打印 语 句并 调 用代 码 ( 5 )使代 码 ( 1 )的 线 程 进 入可 执 行状 态 ,然后代 码 ( 2 )的 线 程 结 束。当代 码 ( 1 )的 线 程重新 执 行后,它接着 执 行 for() 循 环 一直到 shareVar=10 ,然后打印 shareVar 。
3.2 wait() 、 notify() 和 synchronized
waite() 和 notify() 因 为 会 对对 象的 “ 锁标 志 ” 进 行操作,所以它 们 必 须 在 synchronized 函数或 synchronized block 中 进 行 调 用。如果在 non-synchronized 函数或 non-synchronized block 中 进 行 调 用, 虽 然能 编译 通 过 ,但在运行 时 会 发 生 IllegalMonitorStateException 的异常。
例 18 :
class TestThreadMethod extends Thread{
public int shareVar = 0;
public TestThreadMethod(String name){
super(name);
new Notifier(this);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<5; i++){
shareVar++;
System.out.println("i = " + shareVar);
try{
System.out.println("wait......");
this.wait();
}
catch(InterruptedException e){}
}
}
}
}
class Notifier extends Thread{
private TestThreadMethod ttm;
Notifier(TestThreadMethod t){
ttm = t;
start();
}
public void run(){
while(true){
try{
sleep(2000);
}
catch(InterruptedException e){}
/*1 要同 步 的不是当前 对 象的做法 */
synchronized(ttm){
System.out.println("notify......");
ttm.notify();
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
t1.start();
}
}
运行 结 果 为 :
i = 1
wait......
notify......
i = 2
wait......
notify......
i = 3
wait......
notify......
i = 4
wait......
notify......
i = 5
wait......
notify......
4. wait() 、 notify() 、 notifyAll() 和 suspend() 、 resume() 、 sleep() 的 讨论
4.1 这 两 组 函数 的区 别
1) wait() 使当前 线 程 进 入停滞状 态时 , 还 会 释 放当前 线 程所占有的 “ 锁标 志 ” ,从而使 线 程 对 象中的 synchronized 资 源可被 对 象中 别 的 线 程使用;而 suspend() 和 sleep() 使当前 线 程 进 入停滞状 态时 不会 释 放当前 线 程所占有的 “ 锁标 志 ” 。
2) 前一 组 函数必 须 在 synchronized 函数或 synchronized block 中 调 用,否 则 在运行 时 会 产 生 错误 ;而后一 组 函数可以 non-synchronized 函数和 synchronized block 中 调 用。
4.2 这 两 组 函数的取舍
Java2 已不建 议 使用后一 组 函数。因 为 在 调 用 wait() 时 不会 释 放当前 线 程所取得的 “ 锁标 志 ” , 这样 很容易造成 “ 死 锁 ” 。