1. 程式人生 > >Java守護執行緒的理解筆記

Java守護執行緒的理解筆記

為了體會守護執行緒的作用,我做了一個下載檔案的demo,可以沒事用來測測網速什麼的,其特性如下

1、一共有三個執行緒,分別是主執行緒,下載執行緒,守護執行緒

2、主執行緒啟動下載執行緒和守護執行緒

3、下載執行緒連續下載100個檔案,如果出現異常自動捕獲並進入下一個檔案的下載

4、如果下載執行緒下載某個檔案超過了30秒,就認為是超時,而這個超時的檢測由守護執行緒執行

5、如果守護執行緒發現某個檔案下載超時,就停掉下載執行緒,並想辦法另起一個新的下載執行緒繼續下載

首先我們不使用Java設定的守護執行緒,而是用一個普通執行緒充當“守護執行緒”,也就是設定一個計數器,下載成功一個檔案就+1,加到100後這個偽“守護執行緒”就會自我終止,程式碼如下

package zz.vc.qduedu;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;


public class DownloadTest {

	public static long totalDownloadTime;//全部檔案共消耗的下載時間
	public static long downloadStartTime;//某個檔案下載開始的時間
	public static DownloadThread downloadThread;
	public static int completedFileCount = 0;//下載成功的檔案數
	public static final String url = "https://d25hz2nnwuc2tq.cloudfront.net/images/image/cache/data/2016/10/19/1476866804-800x450-1080x608.webp";
	public static final int total_times = 100;
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		downloadThread = new DownloadThread();
		downloadThread.start();
		AlxDemonThread demonThread = new AlxDemonThread();
		demonThread.start();
	}
	public static class DownloadThread extends Thread{
		public boolean shouldStop = false;//超時後終結此程序
		public void run() {
			for(int i=completedFileCount;i<total_times;i++){
				if(shouldStop)return;
				try {
					long cost = downloadNow(url, i);
					if(shouldStop)return;
					System.out.println("第"+i+"次耗時"+cost);
					
				} catch (Exception e) {
					// TODO: handle exception
					if(shouldStop)return;
					System.out.println("下載失敗");
					e.printStackTrace();
				}
				if(shouldStop)return;
				completedFileCount++;
				totalDownloadTime += System.currentTimeMillis() - downloadStartTime;
				downloadStartTime = 0;
			}
			if(!shouldStop)System.out.println("總耗時=="+totalDownloadTime);
		}
	}
	
	public static class AlxDemonThread extends Thread{
		@Override
		public void run() {
			// TODO Auto-generated method stub
			super.run();
			while (2>1) {
				try {
					Thread.sleep(1000);
//					System.out.println("守護執行緒還活著");
				} catch (InterruptedException e) {
				}
				if(completedFileCount == total_times)return;
				if(System.currentTimeMillis() - downloadStartTime > 30*1000){
//					System.out.println("第"+alreadyTime+"超時了");
					System.out.println("the "+completedFileCount+" time out of time");
					downloadThread.shouldStop = true;
					downloadThread.interrupt();
					downloadThread = new DownloadThread();
					downloadThread.start();
					completedFileCount++;
					totalDownloadTime += 30*1000;
				}
			}
		}
	}
	
	public static long downloadNow(String strUrl,int times) throws IOException{
		downloadStartTime = System.currentTimeMillis();
		URL url = new URL(strUrl);
		HttpsURLConnection httpURLConnection = (HttpsURLConnection) url.openConnection();
        httpURLConnection.setDoInput(true);
        httpURLConnection.setUseCaches(false);
        httpURLConnection.setRequestMethod("GET");
        httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
        httpURLConnection.setRequestProperty("Charset", "UTF-8");
        httpURLConnection.setRequestProperty("Content-Type","multipart/form-data;boundary=" + "******");
        httpURLConnection.connect();
//        System.out.println("響應碼是::"+httpURLConnection.getResponseCode());
        File pictureFile = new File("d:/"+times+".jpg");
        BufferedInputStream bis = new BufferedInputStream(httpURLConnection.getInputStream());
        FileOutputStream outputStream = new FileOutputStream(pictureFile);
        byte[] buffer = new byte[1024];
        int len = 0;
        BufferedOutputStream out = new BufferedOutputStream(outputStream);
        while ((len = bis.read(buffer)) != -1) {
        	if(System.currentTimeMillis() - downloadStartTime > 30*1000)throw new RuntimeException("超時");
            out.write(buffer, 0, len);
        }
        out.flush();
        out.close();
        bis.close();
        return System.currentTimeMillis()-downloadStartTime;
	}

}

執行上面的程式,可以在控制檯看見每次檔案下載的耗時,如果出現了異常,並不會中斷子執行緒,而是會開始下載下一個檔案

但是如果偽“守護執行緒”發現了某個檔案下載超時,就會呼叫downloadTread.interrupt()方法停止當前的下載執行緒並且重新new一個下載執行緒繼續之前的進度下載。

在這個地方,我發現

Thread.interrupt()方法並不會立即停止當前執行緒,當前執行緒會繼續執行十幾秒的時間

我的解決方法是修改Tread裡的一個私有變數shouldStop,在跨過耗時操作之後檢查這個私有變數,如果被修改過那麼就return掉當前執行緒。

現在,我們斗膽把這個偽“守護執行緒”程式設計真的守護執行緒,用setDaemon(true)在start()之前實現。

public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		downloadThread = new DownloadThread();
		downloadThread.start();
		AlxDemonThread demonThread = new AlxDemonThread();
		demonThread.setDaemon(true);
		demonThread.start();
	}
然後把我們用於停止偽“守護執行緒”的計數器去掉
//if(completedFileCount == total_times)return;

然後重新執行這個程式

1、如果下載很順暢沒有任何一個檔案超時,那麼守護執行緒會隨著下載執行緒的終止而終止

2、如果有一個檔案下載超時了,守護執行緒會自動new執行緒,但是這個執行緒和守護執行緒跑了沒一會就停了,守護執行緒退出。

得出如下幾個結論

1、守護執行緒由主執行緒建立,守護主執行緒,同樣也守護主執行緒建立的所有子執行緒。

2、主執行緒如果終止(包括crash),由主執行緒建立的子執行緒並不會終止,守護執行緒也不會終止。

3、守護執行緒建立的所有執行緒都是守護執行緒,當父守護執行緒守護的執行緒終止,父守護執行緒及其建立的子守護執行緒都會一起停止

第三條有點拗口,簡單解釋一下就是:當守護執行緒把下載超時的執行緒停止掉之後,這個下載執行緒並不會立即停掉,此時守護執行緒又建立了一個下載執行緒,而這個由守護執行緒建立的下載執行緒並不好用,它會莫名其妙的隨著上個下載執行緒的終止而終止。原因就是這個新下載執行緒其實時候守護執行緒,守護主執行緒(其實就是守護父守護執行緒,也就間接守護舊的下載執行緒),所以沒等下完就自動終止了。

那麼看來通過簡單粗暴的設定守護執行緒並不好用,那麼我們根據“守護執行緒會守護建立它的執行緒”的原理,讓下載執行緒建立一個執行緒守護它自己,當這個守護執行緒發現下載執行緒超時以後,通知主執行緒建立一個新的下載執行緒進行下載,同時也有一個新的守護執行緒守護它

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Random;

import javax.net.ssl.HttpsURLConnection;


public class CopyOfDownloadTest {

	public static long total;
	public static long startTime;
	public static int alreadyTime = 0;
	public static final String url = "https://d25hz2nnwuc2tq.cloudfront.net/images/image/cache/data/2016/10/19/1476866804-800x450-1080x608.webp";
	public static final int total_times = 100;
	
	private static Random threadLock = new Random();//執行緒鎖
	
	public static void main(String[] args) throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		new DownloadThread().start();
		
			while (1<2) {
				synchronized (threadLock) {//讓主執行緒持有執行緒鎖
					threadLock.wait();//鎖住主執行緒
				}
				System.out.println("主執行緒解鎖,準備重新new下載執行緒");
				new DownloadThread().start();
			}
		
	}
	
	public static class DownloadThread extends Thread{
		public boolean shouldStop = false;//超時後終結次程序
		public void run() {
			AlxDemonThread demonThread = new AlxDemonThread(this);
			demonThread.setDaemon(true);
			demonThread.start();
			for(int i=alreadyTime;i<total_times;i++){
				if(shouldStop)return;
				try {
					long cost = downloadNow(url, i);
					if(shouldStop)return;
					System.out.println("第"+i+"次耗時"+cost);
					
				} catch (Exception e) {
					// TODO: handle exception
					if(shouldStop)return;
					System.out.println("下載失敗");
					e.printStackTrace();
				}
				if(shouldStop)return;
				alreadyTime++;
				total += System.currentTimeMillis() - startTime;
				startTime = 0;
			}
			if(!shouldStop)System.out.println("總耗時=="+total);
		}
	}
	
	public static class AlxDemonThread extends Thread{
		private DownloadThread mDownloadThread = null;
		public AlxDemonThread(DownloadThread t) {
			// TODO Auto-generated constructor stub
			this.mDownloadThread = t;
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			super.run();
			while (2>1) {
				try {
					Thread.sleep(1000);
//					System.out.println("守護執行緒還活著");
				} catch (InterruptedException e) {
				}
				if(alreadyTime == total_times)return;
				if(System.currentTimeMillis() - startTime > 30*1000){
					System.out.println("第"+alreadyTime+"超時了");
					mDownloadThread.shouldStop = true;
					mDownloadThread.interrupt();
					alreadyTime++;
					total += 30*1000;
					synchronized (threadLock) {
						threadLock.notify();
					}
					return;//停掉守護執行緒,防止再new一個子執行緒
				}
				//因為是守護執行緒,所以在下載執行緒結束後會自動停止
			}
		}
	}
	
	public static long downloadNow(String strUrl,int times) throws IOException{
		startTime = System.currentTimeMillis();
		URL url = new URL(strUrl);
		HttpsURLConnection httpURLConnection = (HttpsURLConnection) url.openConnection();
        httpURLConnection.setDoInput(true);
        httpURLConnection.setUseCaches(false);
        httpURLConnection.setRequestMethod("GET");
        httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
        httpURLConnection.setRequestProperty("Charset", "UTF-8");
        httpURLConnection.setRequestProperty("Content-Type","multipart/form-data;boundary=" + "******");
        httpURLConnection.connect();
//        System.out.println("響應碼是::"+httpURLConnection.getResponseCode());
        File pictureFile = new File("d:/speed/test"+times+".jpg");
        BufferedInputStream bis = new BufferedInputStream(httpURLConnection.getInputStream());
        FileOutputStream outputStream = new FileOutputStream(pictureFile);
        byte[] buffer = new byte[1024];
        int len = 0;
        BufferedOutputStream out = new BufferedOutputStream(outputStream);
        while ((len = bis.read(buffer)) != -1) {
        	if(System.currentTimeMillis() - startTime > 30*1000)throw new RuntimeException("超時");
            out.write(buffer, 0, len);
        }
        out.flush();
        out.close();
        bis.close();
        return System.currentTimeMillis()-startTime;
	}

}

上面的程式中,守護執行緒由下載子執行緒建立,守護下載子執行緒。當守護執行緒發現下載執行緒超時之後,就會終止下載執行緒,然後通知主執行緒再重新建立一個下載執行緒繼續之前的下載,然後這個守護執行緒會隨著要終止的下載執行緒一起結束。一對新的下載執行緒和守護執行緒繼續當前的下載任務。

但是上面的程式在下載完成之後並不會退出,因為主執行緒還在wait()中。所有的下載子執行緒和守護執行緒均退出。

從這裡可以得出一個結論:

1、object.wait()和object.notify必須在synchronized(object){}程式碼塊中執行,否則會報java.lang.IllegalMonitorStateException異常

2、一個執行緒wait()之後並不會結束。並且守護它的守護執行緒也不會結束。

3、主執行緒也可以wait()

4、主執行緒出現crash崩潰了之後,主執行緒開啟的子執行緒會繼續執行。由主執行緒產生的守護執行緒會守護主執行緒產生的子執行緒。