因为马上毕业了,最近一直在复习Java的基础知识,多线程当然是重点了,今天上午一直在看线程的生命阶段,其中有过时的方法suspend用来挂起一个线程。而关于该方法为何被抛弃了,看了开发文档中是这么描述的:【方法已经遭到反对,因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用resume
之前锁定该监视器,则会发生死锁。】
看完上面的官方文档,光看文字把头都看晕了,感觉sleep和suspend对挂起线程的操作感觉又模糊了不少,尤其是对锁的控制上面。
因此就模拟了两个线程,代码如下:
package a.b;
public class Test {
private static final int INDEX = 10;
public static void main(String[] args) {
try {
// 定义线程
Thread tchild = new Thread(new Runnable() {
public void run() {
try {
int a = 0;
for (long i = 0; i < 1000000; i++) {
while (a < INDEX)/** 如果在执行这句的时候调用了tchild.suspend(),那就不会导致死锁 */
a++;
}
System.out.println(i);
/**
* 如果正好在执行上面这句的时候调用了tchild.suspend(),
* 由于println()方法里面保持有锁,
* 因此,在本线程挂起的时候其他的线程就无法使用println方法中所持有的锁,
* 这就导致suspend方法容易导致死锁的原因。
*/
a = 0;/** 如果在执行这句的时候调用了tchid.suspend(),那就不会导致死锁 *//
}
} catch (Throwable e) {e.printStackTrace();}
}//run
});
tchild.start();/**开启子线程*/
Thread.sleep(2000);/**让主线程停止2s,子线程继续运行2s*/
tchild.suspend();/**让子线程挂起*/
/**让子线程挂起的情况下再执行主线程中的打印操作*/
for (long i = 0; i < 1000000; i++) {
System.out.println("主线程"+i);
}
tchild.resume();//激活被挂起的线程 } catch (Throwable ex) { ex.printStackTrace(); } }
}
实验结果是:
1)当常量INDEX=10的时候,输出的结果大部分情况下是:运行到:
….
子线程152463
子线程152464
子线程152465
此时,打印了子线程的信息过了两秒后,子线程被挂起,但是主线程中的内容却没有继续执行,这说明主线程已经被阻塞了;
原因是:子线程在运行到System.out.println(i);这一句的时候被suspend()方法挂起了,而由于该句调用的时候使用了锁,
即out中的常量public final static PrintStream out = null;此处的out对象就是方法println中使用的锁对象;因此在被suspend挂起后子线程任然持有锁-out常量,所以当主线程运行for循环打印信息的时候System.out.println(“主线程”+i);,主线程根本拿不到锁,因此造成线程死锁;
2)当常量INDEX=100000000的时候,输出结果大部分情况下是,运行到:
…..
主线程999998
主线程999999
子线程2605
子线程2606
…
子线程999998
子线程999999
,同样的道理因为子线程并不是在运行System.out.println(i);这一句的时候被suspend()方法挂起的,所以就不会持有打印语句中的锁-out常量,因此不会产生死锁现象。
综合上面的分析:
调用suspend方法来挂起一个线程A的时候,如果这好在执行一端具有同步锁的代码块的时候被挂起,那这个同步锁是不会被释放的,那么当线程B执行时,如果内部代码需要使用到该锁,而此时的锁是被线程B中代码块持有的,此时就会导致线程死锁。这就是为什么suspend不安全的地方,因为它无法控制线程内部代码块持有的锁的释放。
关于sleep调用后不会释放锁是指的,当线程A的同步代码块中调用了sleep的使用,该代码块持有的锁不会被释放,那么线程B,C,..去执行具有相同锁对象的代码块的时候就会被阻塞。