歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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崩潰了之後,主線程開啟的子線程會繼續執行。由主線程產生的守護線程會守護主線程產生的子線程。

Copyright © Linux教程網 All Rights Reserved