首先,我们应该知道此二类用来实现线程的暂停和恢复,但是最终这两个方法被官方废弃了呢,以至于编写代码的时候都用删除线提示不推荐使用,虽然suspend方法是过期作废的方法,但研究过期作废的原因还是很有必要的
方法Suspend带来的缺点1-锁独占
情况1:
锁独占带来的方法阻塞,为了演示这一情况,我特意写了一个出现此情况的代码
import java.util.concurrent.TimeUnit;
public class SuspendAndResumeThread {
public synchronized void print() {
System.out.println(Thread.currentThread().getName()+"\tprint!");
try {
TimeUnit.DAYS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class Test {
@org.junit.jupiter.api.Test
public void test() throws InterruptedException {
final SuspendAndResumeThread t = new SuspendAndResumeThread();
System.out.println("suspend和resume对线程锁独占的缺点");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
t.print();
}
});
t1.setName("t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
t.print();
}
});
t2.setName("t2");
t1.suspend();
t2.start();
}
}
在这段代码中,我们定义了SuspendAndResumeThread类,在其中定义了一个同步方法print,输出当前线程,并做延时操作,用来模拟线程一直占用锁的情况
然后在测试方法中,我们定义并开启了两个线程,t1和t2,我们使t1和t2相对的同时去调用print方法,但是通过相应代码顺序的调整,确保t1要尽可能先于t2进入同步方法中,并占用锁,在这种情况下,我们将t1线程进行暂停,然后开启t2线程,通过以下输出结果
可以发现,t2无法进入print线程,由此可知,如果Suspend方法使用不当,极易造成公共同步对象锁被独占,其他线程无法访问公共对象的结果
情况2:
锁独占带来的线程无法终止,下面我将再编写一个此情况
class Test2 {
private int i = 0;
public void increment() {
while (true){
i++;
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
}
Thread t = new Thread(new Runnable() {
@Override
public void run() {
increment();
}
});
@org.junit.jupiter.api.Test
public void test() throws InterruptedException {
t.start();
TimeUnit.SECONDS.sleep(1);
t.suspend();
System.out.println("main end");
}
}
通过输出我们可以发现
main end是不会输出的,IDE也是一直未终止
那么为什么会出现这种情况呢?这里我们需要看一下println的源码
public void println(String x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
在源码中,可以看到首先它检查调用println的方法是否是PrintStream类本身,如果是,则调用私有方法writeln(String x)执行输出操作
若不是,则进入else分支,在这里它使用synchronized来确保线程安全的执行操作,在同步块中,首先调用print(x)方法对给定字符输出到标准输出,然后调用newLine进行换行,这种设计确保了多个线程同时调用 println
方法时的线程安全性,因为只有一个线程能够进入同步块并执行输出操作,其他线程需要等待当前线程执行完毕并释放锁资源后才能继续执行。
回到现在的问题来看一切清晰明朗,t线程拿到了println的锁对象,但是它暂停了,它无法释放自己拥有的锁,而main线程想要输出需要获得t拥有的println锁对象,于是死锁产生了,这样mian线程就会永远等待t,IDE也会一直转圈圈
方法Suspend带来的缺点2-数据不完整
这里,我写了一段代码用来演示这种情况
class Test3 {
private String name = "zhangsan";
private String password = "123456";
public void setData(String name,String password){
this.name = name;
if(Thread.currentThread().getName().equals("t")){
System.out.println("t线程暂停");
Thread.currentThread().suspend();
}
this.password = password;
}
public void getPassword(){
System.out.println(this.name+"\t"+this.password);
}
@org.junit.jupiter.api.Test
public void test() throws InterruptedException {
final Test3 test3 = new Test3();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
test3.setData("lisi","000000");
}
});
t.setName("t");
t.start();
Thread.sleep(2000);
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
test3.getPassword();
}
});
t2.setName("t2");
t2.start();
}
}
通过输出可见
数据的修改出现了错误的情况,数据变得不完整,同样的这种情况让我想到之前模拟的那种多线程的错误情况,A和B同时修改一个实例类中的username和password属性,结果A在修改用户名后线程模拟休眠4s,此时B线程也来修改这一实例,当B将用户名和密码都修改完成后,A线程醒来修改实例密码,那么就会出现A的用户名B的密码的线程安全带来的错误问题,由此可见,Suspend方法确实在数据的一致性方面也有很大的欠缺。
综上所述,Suspend线程暂停方法与Resume恢复线程方法被淘汰确实是时代的选择