java安全編碼指南之:Thread API調用規則

簡介

java中多線程的開發中少不了使用Thread,咱們在使用Thread中提供的API過程當中,應該注意些什麼規則呢?java

一塊兒來看一看吧。git

start一個Thread

Thread中有兩個方法,一個是start方法,一個是run方法,兩個均可以調用,那麼兩個有什麼區別呢?github

先看一下start方法:數組

public synchronized void start() {

        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

    private native void start0();
複製代碼

start()是一個synchronized的方法,經過它會去調用native的start0方法,而最終將會調用Thread的run()方法。安全

咱們知道,建立一個Thread有兩種方式,一種是傳入一個Runnable,一個是繼承Thread,並重寫run()方法。markdown

若是咱們直接調用Thread的run()方法會發生什麼事情呢?多線程

先看一下run方法的定義:函數

public void run() {
        if (target != null) {
            target.run();
        }
    }
複製代碼

默認狀況下, 這個target就是一個Runnable對象,若是Thread是經過Runnable來構建的話,調用Thread.run()會在當前線程中運行run方法中的內容。oop

若是Thread是以其形式構建,而且沒有從新run()方法,那麼直接調用Thread.run()將什麼都不會作。this

public void wrongStart(){
        Runnable runnable= ()-> System.out.println("in thread running!");
        Thread thread= new Thread(runnable);
        thread.run();
    }

    public void correctStart(){
        Runnable runnable= ()-> System.out.println("in thread running!");
        Thread thread= new Thread(runnable);
        thread.start();
    }
複製代碼

因此,上面兩種調用方式,只有第二種是正確的。

不要使用ThreadGroup

Thread中有個字段類型是java.lang.ThreadGroup,這個主要是用來給Thread進行分組,咱們看下Thread的這個構造函數:

public Thread(ThreadGroup group, Runnable target) {
        this(group, target, "Thread-" + nextThreadNum(), 0);
    }
複製代碼

上面的構造函數能夠在傳入runnable的同時傳遞一個ThreadGroup對Thread進行分組。

若是沒有指定ThreadGroup,那麼將會爲其分配一個默認的default group。

ThreadGroup是作什麼的呢?ThreadGroup是java 1.0引入的方法,主要是一次性的對一組thread進行操做。咱們能夠調用ThreadGroup.interrupt()來一次性的對整個Group的Thread進行interrupts操做。

雖然ThreadGroup提供了不少有用的方法,可是其中不少方法都被廢棄了,好比:allowThreadSuspension(), resume(), stop(), 和 suspend(),而且ThreadGroup中還有不少方法是非線程安全的:

  • ThreadGroup.activeCount()

這個方法主要是用來統計一個ThreadGroup中活動的線程個數,這個方法會統計還未啓動的線程,同時也會受系統線程的影響,因此是不許確的。

  • ThreadGroup.enumerate()

這個方法是將ThreadGroup和子group的線程拷貝到一個數組中,可是若是數組過小了,多餘的線程是會被自動忽略的。

ThreadGroup自己有一個 stop() 方法用來中止全部的線程,可是stop是不安全的,已經被廢棄了。

那麼咱們該怎麼去安全的中止不少個線程呢?

使用executor.shutdown()就能夠了。

不要使用stop()方法

剛剛講了ThreadGroup中不要調用stop()方法,由於stop是不安全的。

調用stop方法會立馬釋放線程持有的全部的鎖,而且會拋出ThreadDeath異常。

由於會釋放全部的鎖,因此可能會形成受這些鎖保護的對象的狀態發生不一致的狀況。

替代的方法有兩種,一種是使用volatile flag變量,來控制線程的循環執行:

private volatile boolean done = false;

    public void shutDown(){
        this.done= true;
    }

    public void stopWithFlag(){

        Runnable runnable= ()->{
            while(!done){
                System.out.println("in Runnable");
            }
        };

        Thread thread= new Thread(runnable);
        thread.start();
        shutDown();
    }
複製代碼

另一種方法就是調用interrupt(), 這裏咱們要注意interrupt()的使用要點:

  1. 若是當前線程實例在調用Object類的wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)方法,或者在該實例中調用了Thread.sleep(long)或Thread.sleep(long,int)方法,而且正在阻塞狀態中時,則其中斷狀態將被清除,並將收到InterruptedException。

  2. 若是此線程在InterruptibleChannel上的I/O操做中處於被阻塞狀態,則該channel將被關閉,該線程的中斷狀態將被設置爲true,而且該線程將收到java.nio.channels.ClosedByInterruptException異常。

  3. 若是此線程在java.nio.channels.Selector中處於被被阻塞狀態,則將設置該線程的中斷狀態爲true,而且它將當即從select操做中返回。

  4. 若是上面的狀況都不成立,則設置中斷狀態爲true。

先看下面的例子:

public static void main(String[] args) {
        Runnable runnable= ()->{
            while (!Thread.interrupted()) {
             System.out.println("in thread");
            }
        };
        Thread thread= new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
複製代碼

咱們在while循環中調用了Thread.interrupted()方法用來判斷線程是否被設置了中斷位,而後在main方法中調用了thread.interrupt()來設置中斷,最終能夠正確的中止Thread。

注意,這裏運行的Thread並無被阻塞,因此並不知足咱們上面提到的第一個條件。

下面咱們再看一個例子:

public static void main(String[] args) {
        Runnable runnable= ()->{
            while (!Thread.interrupted()) {
             System.out.println("in thread");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread= new Thread(runnable);
        thread.start();
        thread.interrupt();
    }
複製代碼

這個例子和上面的例子不一樣之處就是在於,Thread中調用了sleep方法,致使Thread被阻塞了,最終知足了第一個條件,從而不會設置終端位,只會拋出InterruptedException,因此這個例子中線程是不會被中止的,你們必定要注意。

wait 和 await 須要放在循環中調用

爲何要放在循環中呢?由於咱們但願wait不是被錯誤的被喚醒,因此咱們須要在wait被喚醒以後,從新檢測一遍條件。

錯誤的調用是放在if語句中:

synchronized (object) {
  if (<condition does not hold>) {
    object.wait();
  }
  // Proceed when condition holds
}
複製代碼

正確的方法是放在while循環中:

synchronized (object) {
  while (<condition does not hold>) {
    object.wait();
  }
  // Proceed when condition holds
}
複製代碼

本文的代碼:

learn-java-base-9-to-20/tree/master/security

本文已收錄於 www.flydean.com/java-securi…

最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

歡迎關注個人公衆號:「程序那些事」,懂技術,更懂你!

相關文章
相關標籤/搜索