併發編程系列之線程的啓動終止

前言

上節我們對線程有了個基本的概念和認識,從線程狀態轉變過程我們也已經知道了線程通過調用start方法進行啓動,直到run方法執行線程結束,今天我們就來詳細的說說啓動和終止線程的細節,OK,讓我們開始今天的併發之旅吧。

 

創建線程

在使用一個線程之前我們需要先構造線程,即new一個線程

Thread thread = new Thread();

線程對象在構建的時候需要提供線程所需要的屬性,如線程組、優先級等等,下面我們看下如下的源代碼:

private void init(ThreadGroup g, Runnable target, String name,
                     long stackSize) {
       // 如果新線程名字爲null,拋出異常
       if (name == null) {
           throw new NullPointerException("name cannot be null");
       }
       // 獲取該線程的父線程
       Thread parent = currentThread();
       // 獲取安全管理組件
       SecurityManager security = System.getSecurityManager();
       if (g == null) {
           // 如果安全組件不爲空,就調用SecurityManager的線程組
           if (security != null) {
               g = security.getThreadGroup();
           }
           // 如果SecurityManager爲空,並且該線程的線程組也爲空,則調用其父線程的線程組
           if (g == null) {
               g = parent.getThreadGroup();
           }
       }
       // 顯示通過 允許線程訪問線程組
       g.checkAccess();

       // 檢查訪問權限
       if (security != null) {
           if (isCCLOverridden(getClass())) {
               security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
           }
       }
       // 對線程組中未使用的線程計數器+1
       g.addUnstarted();
       // 調用父線程的線程組
       this.group = g;
       // 調用父線程守護線程
       this.daemon = parent.isDaemon();
       // 調用父線程的優先級
       this.priority = parent.getPriority();
       // 將字符串轉換爲新的字符數組
       this.name = name.toCharArray();
       // 加載父線程的ContextClassLoader
       if (security == null || isCCLOverridden(parent.getClass()))
           this.contextClassLoader = parent.getContextClassLoader();
       else
           this.contextClassLoader = parent.contextClassLoader;
       this.inheritedAccessControlContext = AccessController.getContext();
       this.target = target;
       setPriority(priority);
       // 加載父線程的ThreadLoad
       if (parent.inheritableThreadLocals != null)
           this.inheritableThreadLocals =
               ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       // 指定堆棧大小
       this.stackSize = stackSize;
       // 設置線程ID
       tid = nextThreadID();
   }

從上面源碼我們可以看到,一個新的線程是由其父線程來進行空間分配的,子線程繼承父線程的優先級,是否爲守護線程、contextClassLoader以及ThreadLocal,最後分配一個唯一的線程ID,新的線程就被創建完畢,在堆內存中等待着被運行。

也可以通過構建Runnable對象來構建線程:

Thread t1 = new Thread(new Runnable() {
     @Override
     public void run() {
         ...
     }
   }, "t1");

啓動線程

線程創建完畢,我們就可以開始使用該線程了,啓動一個線程很簡單,直接使用start方法

thread.start();

我們再來對start方法的源碼進行分析:

/**
* 該方法不是給main線程和系統線程調用的
* 由虛擬機創建和設置的組線程
*/
public synchronized void start() {
       // 0代表線程的狀態NEW,初始化狀態,如果線程不是初始化狀態,則拋出異常
       if (threadStatus != 0)
           throw new IllegalThreadStateException();
       
       // 通知線程組,該線程即將被啓動,添加到組的運行線程列表中,同時未使用線程數計數器-1
       group.add(this);

       // 啓動成功標識符
       boolean started = false;
       try {
           start0();
           started = true;
       } finally {
           try {
               if (!started) {
                   // 如果啓動不成功,線程組做相應啓動失敗處理
                   group.threadStartFailed(this);
               }
           } catch (Throwable ignore) {
               // 如果什麼都不做,則
           }
       }
   }

執行start方法之後,當前線程(父線程)就會同步通知虛擬機,只要線程規劃器空閒,就應該立即啓動該線程(調用start方法的線程)。

線程中斷

線程中斷表示一個運行中的線程是否被其他線程進行中斷操作,中斷在線程中是由一個Boolean值來標識的,我們看下如何判斷當前線程中斷狀態和相關操作:

  • 判斷線程是否被中斷:isInterrupted方法

public static void main(String[] args) throws Exception{
       Thread thread1 = new Thread();
       thread1.start();
       System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());
   }
結果:線程:Thread-0 是否被中斷:false
  • 中斷線程方法:interrupt方法

thread1.interrupt();
System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());

結果:線程:Thread-0是否被中斷:true
  • 清除中斷標識:currentThread().interrupt()方法

public static void main(String[] args) throws Exception{
       Thread thread1 = new Thread();
       thread1.start();
       thread1.interrupt();
       System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());
       thread1.currentThread().interrupt();
       System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());
   }
結果:
線程:Thread-0是否被中斷:true
線程:Thread-0是否被中斷:false

 

安全的終止線程

上面提到的中斷方式是一種比較常見的終止方式,除此之外還有2種方式,一個是使用一個標誌位來通知線程終止,還有一個就是使用stop方法(不推薦,下面會詳解),下面我們先看看使用標誌位和中斷如何終止線程:

  • 使用標誌位Boolean值

public class ThreadStartDemo {
   // 線程終止標識位
   static volatile Boolean flag = false;
   static int time1 = 0;
   static int time2 = 0;

   public static void main(String[] args) throws Exception {
       Thread t1 = new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("線程1啓動");
               while (!flag) {
               }
               time2 = Integer.parseInt(DateUtil.getNowTimestamp());
               int i = time2 - time1;
               System.out.println("線程t1退出,等待時間爲" + i + "秒");
           }
       }, "t1");

       Thread t2 = new Thread(new Runnable() {
           @Override
           public void run() {
               time1 = Integer.parseInt(DateUtil.getNowTimestamp());
               System.out.println("線程2啓動,5秒後修改flag的值");
           }
       }, "t2");

       t1.start();
       t2.start();
       t2.sleep(5000);
       flag = true;
   }
}

運行結果如下:

5秒過後:注意看,線程以及終止運行

  • 使用中斷機制終止線程

public class ThreadExitDemo extends Thread {
   public void run() {
       System.out.println("線程運行中!!!");
       System.out.println("請輸入任意鍵盤值來發出中斷信號");
   }

   public static void main(String[] args) throws Exception {
       Thread thread = new ThreadExitDemo();
       thread.start();
       System.in.read();
       thread.interrupt();
       thread.join();
       System.out.println("線程已經退出!!!");
   }
}

運行結果如下:

線程中幾個廢棄的方法

在線程運行過程中還有三個被拋棄的方法,分別是suspend()、resume()、stop()方法,分別代表暫停、恢復和停止的意思,那麼爲什麼這3個方法被廢棄了呢?

  • suspend方法在被調用後,線程不會釋放已經佔有的資源(如鎖),而是佔着資源進入睡眠狀態,這樣容易引起死鎖問題;

  • resume和suspend是成對出現的,既然suspend被拋棄了,當然好基友resume也就沒有用武之地了,也是不被推薦使用的方法;

  • stop方法在終止一個線程時,不會保證線程的資源正常釋放,通常是沒有給予線程完成資源釋放工作的機會,這就會導致程序可能工作在不確定的狀態下,導致一個線程任務不能完整的執行完就被直接終止,程序執行的正確性得不到保證;

 

以上就是今天所講的線程啓動和終止的相關內容,希望通過這篇文章,你能對如何正確的啓動和關閉一個線程有所掌握,感謝您的閱讀!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章