java线程学习(二): 终止线程讲解:Stop()方法(后附如何正确终止线程)

本章来学习Java的stop线程终止方法;
老规矩,先看源码:

	@Deprecated
	public final void stop() {
		SecurityManager var1 = System.getSecurityManager();
		if (var1 != null) {
			this.checkAccess();
			if (this != currentThread()) {
				var1.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
			}
		}
		if (this.threadStatus != 0) {
			this.resume();
		}
		this.stop0(new ThreadDeath());
	}
private native void stop0(Object var1);

看注解就知道,stop方法已经被注为废弃方法了,为什么呢,看看源码也大概知道一些原因:

  • stop在一系列判断后,后面执行了本地方法stop0(),该方法直接简单粗暴地停止了当前的线程,而如果在高并发或者大大的循环时,在直接终止线程时,如果一个对象已被修改,但又修改了一半的时候,直接终止的话就有可能被其他的信息去赋值,导致信息不正确,数据库有可能就会被永久地修改了,所以stop方法实际上是一个存在较大安全隐患的一个方法,所以,设为废弃方法也是情有可原。
  • 详细地说,Thread.stop()方法在结束线程时,会直接终止线程,并且会立即释放这个线程所有的锁,而这些锁恰恰是用来维持对象一致性的。如果此时,写线程写入数据正写到一半,并强行终止,那么对象就会被写坏,同时由于锁已经释放,另外一个等待该锁的读线程就会顺理成章地读到了这个不一致的对象,悲剧也就由此发生,而且,在信息错误后,你还很难排查到底是什么错!!!!

那官方到底是如何解释为何废弃这个方法的呢,我们参考下Oracle在concurrency包下的解释
Why is Thread.stop deprecated?

  • Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.

简单翻译下:

  • 因为它本质上是不安全的。停止一个线程会导致它解锁该条线程的所有锁。(当ThreadDead异常在堆栈上传播时,监视器被解锁。)如果以前受这些监视器保护的任何对象处于不一致状态,其他线程现在可以以不一致状态查看这些对象。这些物体可能被损坏了。当线程对损坏的对象进行操作时,可能导致很多不一致的行为。这种行为可能是微妙的,并且难以检测,或者可能是明显的。与其他未检查的异常不同,ThreadDead会静默地杀死线程;因此,用户没有收到程序可能被破坏的警告。这个情况可以在实际损害发生后的任何时间,甚至在未来数小时或数天内显现。

下面我们举个例子来看下:

public class Stop_demo {
	private static User user=new User();

	public static void main(String[] args) throws InterruptedException {
		new ReadThread().start();
		//频繁创建线程
		while (true) {
			Thread thread=new WriteThread();
			thread.start();
			Thread.sleep(150);
			thread.stop();
		}
	}
	//频繁写线程
	public static class WriteThread extends Thread{

		@Override
		public void run() {
			while (true) {
				synchronized (user) {
					Integer age= (int) ((System.currentTimeMillis()/1000));
					user.setAge(age);
					//休息100毫秒
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					//把名字和年龄赋同样的值:
					user.setName(String.valueOf(age));
				}
				Thread.yield();
			}
		}
	}

	//频繁读线程
	public static class ReadThread extends Thread{
		@Override
		public void run() {
			while (true) {//一直监听user的信息
				synchronized (user) {
					if (user.getName()!=null&&user.getAge()!=Integer.parseInt(user.getName())) {
						System.out.println("信息不正确了!!!!!");
						System.out.println(user.toString());
					}
				}
				Thread.yield();
			}
		}

	}

	//实体类
	public static class User{
		private String name;
		private Integer age;
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public Integer getAge() {
			return age;
		}
		public void setAge(Integer age) {
			this.age = age;
		}
		@Override
		public String toString() {
			return "User [name=" + name + ", age=" + age + "]";
		}
	}
}

例子中,通过频繁去创建线程,以及使用user对象锁对User的数据进行保护,在线程stop时,就有可能会出现共享锁user的name或者age信息还没来得及同时改变,就已经被终止了,而读取线程读取到数据后发现user的两者属性不一致,就会输出响应的信息:
在这里插入图片描述
可见,stop方法在高并发时,就会出现数据不对称的情况,严重者会导致数据库的信息被永久修改。所以该方法要慎用!!

那么,我们要如何正确终止线程?
方法实际上很简单,通过true/false判断就可以了,我们添加多一个判断的方法:
在这里插入图片描述
完整案例代码:

package stop_demo;

public class Stop_demo {

	private static User user=new User();


	public static void main(String[] args) throws InterruptedException {
		new ReadThread().start();
		//频繁创建线程
		while (true) {
			WriteThread thread=new WriteThread();
			thread.start();
			Thread.sleep(150);
			thread.stopThread();
		}
	}
	//频繁写线程
	public static class WriteThread extends Thread{

		private volatile boolean flag=false;
		//添加终止线程的方法
		public void stopThread() {
			flag=true;
		}
		@Override
		public void run() {
			while (true) {
				//修改数据时提前判断是否已经终止
				if (flag) {
					System.out.println("当前线程:"+Thread.currentThread().getName()+"已经终止");
					break;
				}
				synchronized (user) {
					Integer age= (int) ((System.currentTimeMillis()/1000));
					user.setAge(age);
					//休息100毫秒
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					//把名字和年龄赋同样的值:
					user.setName(String.valueOf(age));
				}
				Thread.yield();
			}
		}
	}

	//频繁读线程
	public static class ReadThread extends Thread{
		@Override
		public void run() {
			while (true) {//一直监听user的信息
				synchronized (user) {
					if (user.getName()!=null&&user.getAge()!=Integer.parseInt(user.getName())) {
						System.out.println("信息不正确了!!!!!");
						System.out.println(user.toString());
					}
				}
				Thread.yield();
			}
		}

	}

	//实体类
	public static class User{
		private String name;
		private Integer age;
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public Integer getAge() {
			return age;
		}
		public void setAge(Integer age) {
			this.age = age;
		}
		@Override
		public String toString() {
			return "User [name=" + name + ", age=" + age + "]";
		}

	}
}

当然,jdk还是会提供正确终止线程的方法的,那就是 interrupt() 方法,下一章将会讲解到,也建议使用interrupt()方法取代stop()方法
感想:学习一个知识,还是要看看源码,然后网上可以去多了解一点,然后自己写个案例,就可以容易地学到知识啦 ,当然,最好的还是看书,毕竟知识是有价的~~~

猜你喜欢

转载自blog.csdn.net/shenhaiyushitiaoyu/article/details/85006682