1. 程式人生 > >java執行緒學習(二): 終止執行緒講解:Stop()方法(後附如何正確終止執行緒)

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()方法
感想:學習一個知識,還是要看看原始碼,然後網上可以去多瞭解一點,然後自己寫個案例,就可以容易地學到知識啦 ,當然,最好的還是看書,畢竟知識是有價的~~~