天天看點

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的兩者屬性不一緻,就會輸出響應的資訊:

java線程學習(二): 終止線程講解:Stop()方法(後附如何正确終止線程)

可見,stop方法在高并發時,就會出現資料不對稱的情況,嚴重者會導緻資料庫的資訊被永久修改。是以該方法要慎用!!

那麼,我們要如何正确終止線程?

方法實際上很簡單,通過true/false判斷就可以了,我們添加多一個判斷的方法:

java線程學習(二): 終止線程講解:Stop()方法(後附如何正确終止線程)

完整案例代碼:

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()方法

感想:學習一個知識,還是要看看源碼,然後網上可以去多了解一點,然後自己寫個案例,就可以容易地學到知識啦 ,當然,最好的還是看書,畢竟知識是有價的~~~

繼續閱讀