1. 程式人生 > >三、JAVA多執行緒:Thread API詳細介紹 (sleep、Interrupt、Join、TimeUnit、yield、interrupted、執行緒優先順序 )

三、JAVA多執行緒:Thread API詳細介紹 (sleep、Interrupt、Join、TimeUnit、yield、interrupted、執行緒優先順序 )

 本章深入分析了Thread的所有API,熟練掌握Thread的API是學好Thread的前提。

 

執行緒sleep

sleep是一個靜態方法,一共有兩個過載方法,一個需要傳入毫秒數,另一個既要傳入毫秒數又要傳入納秒數。

 

sleep方法介紹

static void sleep(long millis)

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

static void sleep(long millis, int nanos)

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds plus the specified number of nanoseconds, subject to the precision and accuracy of system timers and schedulers.

 

 

 

 

 

sleep方法會使當前執行緒進入指定毫秒數的休眠,暫停執行,雖然給定了休眠時間,但是最終要以系統的定時器和排程器的精度為準

不會放棄monitor鎖的所有權。 每個執行緒的休眠互不影響。

 

使用TimeUnit代替Thread.sleep 

TimeUnit.DAYS          //天

TimeUnit.HOURS         

//小時

TimeUnit.MINUTES       //分鐘

TimeUnit.SECONDS       //秒

TimeUnit.MILLISECONDS  //毫秒

 

TimeUnit.SECONDS.sleep( 5 );

TimeUnit.MINUTES.sleep( 5 );

TimeUnit.HOURS.sleep( 5 );

 

 

 

 

 

 

執行緒yield

yield屬於一種啟發式的方法,會提醒排程器我自願放棄當前的CPU資源,如果CPU的資源不緊張,則會忽略這種提醒。

呼叫yield方法會使當前執行緒從RUNNING狀態切換到RUNNABLE狀態

yield和sleep

 

sleep會導致當前執行緒暫停指定的時間,沒有cpu時間片的消耗

yield只是對CPU排程器一個提示,如果CPU沒有忽略這個提示,它會導致執行緒上下文的切換

sleep會使執行緒斷站block,會在給定的時間內釋放CPU資源

yield會使RUNNING狀態的Thread進入RUNNABLE狀態(如果CPU沒有忽略這個提示)

sleep機會百分百萬恆了給定時間的休眠,但是yield的提示並不一定能擔保

一個執行緒的sleep另一個執行緒呼叫interrupt會捕獲到中斷訊號,而yield則不會。

 

設定執行緒的優先順序

 

void setPriority(int newPriority)

變更執行緒的優先順序

int getPriority()

返回執行緒的優先順序

 

 

 

 

 

執行緒優先順序介紹

理論上優先順序比較高的執行緒會優先被CPU排程。

對於root使用者,他會hint作業系統你要設定的優先順序,否則會忽略

CPU繁忙,設定優先順序獲取更多的CPU時間,但是CPU空閒,幾乎不起作用

不建議在程式設定的時候,使用執行緒優先順序繫結某些特定的業務,或者讓業務嚴重依賴於執行緒優先順序。‘

執行緒優先順序原始碼分析

直接檢視setPriority方法

 

/**
 * Changes the priority of this thread.
 * <p>
 * First the <code>checkAccess</code> method of this thread is called
 * with no arguments. This may result in throwing a
 * <code>SecurityException</code>.
 * <p>
 * Otherwise, the priority of this thread is set to the smaller of
 * the specified <code>newPriority</code> and the maximum permitted
 * priority of the thread's thread group.
 *
 * @param newPriority priority to set this thread to
 * @exception  IllegalArgumentException  If the priority is not in the
 *               range <code>MIN_PRIORITY</code> to
 *               <code>MAX_PRIORITY</code>.
 * @exception  SecurityException  if the current thread cannot modify
 *               this thread.
 * @see        #getPriority
 * @see        #checkAccess()
 * @see        #getThreadGroup()
 * @see        #MAX_PRIORITY
 * @see        #MIN_PRIORITY
 * @see        ThreadGroup#getMaxPriority()
 */
public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

 

優先順序等級  預設 5  最小1,最大 10 。

/**
  * The minimum priority that a thread can have.
  */
 public final static int MIN_PRIORITY = 1;

/**
  * The default priority that is assigned to a thread.
  */
 public final static int NORM_PRIORITY = 5;

 /**
  * The maximum priority that a thread can have.
  */
 public final static int MAX_PRIORITY = 10;

關於執行緒優先順序總結

一般不建議對執行緒設定優先級別更不會讓某些業務嚴重依賴執行緒的優先級別,比如權重。藉助優先順序設定某個任務的權重,這種方式不可取,建議使用預設優先順序( 5 )。 沒有顯示指定, 子執行緒與父執行緒優先順序保持一致。

獲取執行緒ID

獲取執行緒的唯一ID,在整個JVM程序中都是唯一的從0開始逐次遞增。

long getId()

Returns the identifier of this Thread.

 

 

 

獲取當前執行緒

static Thread currentThread()

返回當前執行執行緒的引用

 

 

 

設定執行緒上下文類載入器

 

ClassLoader getContextClassLoader()

Returns the context ClassLoader for this Thread.

 

 

 

獲取執行緒上下文的類載入器,如果沒有修改類的上下文載入器,則保持與父執行緒同樣的類載入器

void setContextClassLoader(ClassLoader cl)

Sets the context ClassLoader for this Thread.

 

 

 

設定執行緒的類載入器,打破JAVA類載入器的父委託機制。

 

執行緒Interrupt

public void interrupt()
public static boolean interrupted()
public boolean isInterrupted()

interrupt

以下方法會使得當前的執行緒進入阻塞狀態,而呼叫當前執行緒的interrupt方法,就會打斷阻塞。

          1. Object的wait方法

          2. Object的wait(long)方法

          3. Object的wait(long,int)方法

          4. Thread的sleep(long)方法

          5. Thread的sleep(long,int)方法

          6. Thread的join方法

          7. Thread的join(long)方法

          8. Thread的join(long,int)方法

          9. InterruptibleChannel的io操作

          10. Selector的wakeup方法

上述若干方法都會使得當前執行緒進入阻塞狀態,若另外的一個執行緒呼叫被阻塞執行緒的interrupt方法,則會打斷這種阻塞,因此這種方法優勢會被成為可中斷的方法。

打斷一個執行緒並不等於該執行緒的生命週期結束,僅僅是打斷了當前執行緒的阻塞狀態。

一旦執行緒在阻塞的情況下被打斷,就會丟擲一個InterruptedExeception的異常,這個異常就像一個singnal(訊號)一樣通知當前執行緒被打斷了。

 

在一個執行緒內部存在著名為interrupt flag的標識, 如果一個執行緒被interrupt,那麼他的flag將會被設定,當時如果當前執行緒正在執行可以中斷的方法被阻塞是,將會呼叫interrupt方法將其中斷,反而會導致flag被清除。執行緒已經死亡的話,interrupt將會忽略。

 

isInterrupted

Thread的一個成員方法,判斷當前執行緒是否被中斷

 

 

中斷方法捕獲了中斷訊號之後,為了不影響執行緒的其他方法執行,會將執行緒的interrupt標識復位。

所以上面所有的輸出都是 false

 

 

 

interrupted

用於判斷當前執行緒是否被中斷,會直接擦除執行緒中的interrupt標識。

如果當前執行緒被打斷了,那麼呼叫第一次interrupted方法會直接返回true,並且立即擦除了interrupt標識。

第二次包括以後的呼叫都會永遠的放回false


 

 

區別interrupted 和isInterrupted

1. Thread.interrupted() 第一次返回true ,第二次程式設計false ,說明interrupted會直接擦除執行緒中的interrupt標識。

2.isInterrupted 呼叫兩次,都是true 說明isInterrupted會直接擦除執行緒中的interrupt標識。

 

 

如上圖,呼叫 Thread.interrupted() 第一次返回true ,第二次程式設計false ,說明interrupted會直接擦除執行緒中的interrupt標識。

 

 

isInterrupted 呼叫兩次,都是true 說明isInterrupted會直接擦除執行緒中的interrupt標識。

 

interrupt注意事項

isInterrupted和interrupted方法都呼叫了同一個本地方法:

/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

ClearInterrupted是用用來控制是否擦除執行緒的interrupt的標識。

isInterrupted方法的原始碼中該引數設定為false,標識不想擦除。

 

執行緒Join

與sleep一樣,他也是一個可中斷的方法,也就是說,如果有其他執行緒執行了對當前執行緒的interrupt操作,他也會捕捉到中斷訊號,並且擦除執行緒的interrupt標識,ThreadAPI為我們提供了三種不同的join方法。具體如下

public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
public final void join(long millis,  int nanos) throws InterruptedException

執行緒join方法詳解

join某個執行緒A, 會使當前執行緒B進入等待,直到執行緒A結束生命週期,或者達到給定的時間。

在此期間執行緒B是處與BLOCKED狀態。

基本用法:

 

 


import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.toList;

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {

        List<Thread> threads = IntStream.range(1,3).mapToObj(ThreadJoin::create).collect(toList());
        threads.forEach(Thread::start);

        for (Thread thread : threads){
            thread.join();
        }

        for (int i = 0 ; i < 10 ; i ++ ) {
            System.out.println(Thread.currentThread().getName() + "#" + i );
            shortSleep();
        }


    }

    private static Thread create(int seq) {
        return new Thread(() -> {
            for (int i = 0 ; i < 10 ; i ++ ) {
                System.out.println(Thread.currentThread().getName() + "#" + i );
                shortSleep();
            }


        }, String.valueOf(seq));

    }

    private static void shortSleep(){

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

 

輸出不同的結果:

 

結論:

建立兩個執行緒,分別啟動,並呼叫每個執行緒的join方法, 執行緒1和執行緒2交替輸出,知道生命週期結束。main執行緒才執行。

join方法會使當前執行緒永遠的等待下去,知道期間被另外的執行緒中斷,或者join的執行緒結束,

當前也可以使用它另外的過載方法。指定毫秒數,在指定的時間到達之後,執行緒退出阻塞。

join方法結合實踐

客戶獲取多個航空公司的航班資訊

 

 

程式碼:

import java.util.List;

public interface FightQuery {
    List<String> get() ;
}

 

 

package com.zl.step3.Flght;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class FightQueryTask extends Thread implements FightQuery {

    private final String origin ;


    private final String destionation  ;

    private final List<String> flightList = new ArrayList<>();


    public FightQueryTask(String airline , String origin , String destionation) {
        super("["+airline+"]");
        this.origin = origin;
        this.destionation = destionation ;
    }

    @Override
    public void run(){
        System.out.printf("%s-query form %s to %s \n" , getName() , origin , destionation) ;

        int randomVal = ThreadLocalRandom.current().nextInt(10);

        try {
            TimeUnit.SECONDS.sleep(randomVal);

            this.flightList.add(getName()+" - " + randomVal);

            System.out.printf("The Fight: %s list query sucessful \n" , getName());


        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }


    @Override
    public List<String> get(){
        return  this.flightList;
    }



}

 

 

package com.zl.step3.Flght;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.util.stream.Collectors.toList;

public class FlightCompanyExample {

    //合作的各大航空公司
    private static List<String> fightCompany = Arrays.asList("CSA","CEA","HNA") ;



    public static void main(String[] args) {

        List<String> results = search("SH","BJ");

        System.out.println("===========resoult=================");

        results.forEach(System.out :: println);

    }

    private static List<String> search(String original, String dest) {

        final List<String> result = new ArrayList<>() ;

        // 查詢航班資訊的執行緒列表
        List<FightQueryTask> tasks = fightCompany.stream().map(f -> createSearchTask(f,original,dest)).collect(toList());

        // 啟動執行緒
        tasks.forEach(Thread::start);


        // 阻塞執行緒
        tasks.forEach(t -> {
            try{
                t.join();
            }catch (InterruptedException e){

            }

        });

        // 輸出結果
        tasks.stream().map(FightQuery::get).forEach(result::addAll);

        return result ;


    }


    private static FightQueryTask createSearchTask(String fight , String original, String dest ){
        return new FightQueryTask(fight,original,dest);
    }




}

 

 

 

 

 

輸出結果:

 

 

 

如何關閉一個執行緒

 

JDK中有一個Deprecated方法stop, 這個方法存在問題,不推薦使用

該方法在關閉執行緒時可能不會釋放掉monitor的鎖,不推薦!!!!

@Deprecated
public final void stop() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        checkAccess();
        if (this != Thread.currentThread()) {
            security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
        }
    }
    // A zero status value corresponds to "NEW", it can't change to
    // not-NEW because we hold the lock.
    if (threadStatus != 0) {
        resume(); // Wake up thread if it was suspended; no-op otherwise
    }

    // The VM can handle all thread states
    stop0(new ThreadDeath());
}

 

正常關閉

1.執行緒結束,生命週期正常結束

2.捕獲中斷訊號,關閉執行緒

    通過檢查interrupt的標識來決定是否退出。

 

import java.util.concurrent.TimeUnit;

public class InterruptThreadExit {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            @Override
            public void run(){
                System.out.println("I whill start work ");

                while (!isInterrupted()) {
//                    System.out.println(" I'm working....");


                }

                System.out.println("I will be exiting .");
            }

        };

        t.start();
        TimeUnit.SECONDS.sleep(1);

        System.out.println("System will be shutdown .");
        t.interrupt();

    }


}

 

執行截圖:

 

3.使用volatile 開關控制

由於interrupt標識很有可能被擦除,或者邏輯單元中不會呼叫任何中斷方法,

所以使用volatile修飾的開關flag關閉執行緒也是一種常用的做法:

 

import java.util.concurrent.TimeUnit;

public class FlagThreadExit {

    static class MyTask extends Thread {
        private volatile boolean closed = false;

        @Override
        public void run(){
            System.out.println("I will start work ");

            while (!closed & !isInterrupted()) {
//                    System.out.println(" I'm working....");


            }

            System.out.println("I will be exiting .");
        }


        public void close(){
            this.closed = true;
            this.interrupt();
        }

    }


    public static void main(String[] args) throws InterruptedException {
        MyTask t = new MyTask();

        t.start();

        TimeUnit.SECONDS.sleep(1);

        System.out.println("System will be shutdown .");
        t.close();

    }


}

 

 

異常退出

程序假死

程序雖然存在,但是沒有日誌輸出,程式不做任何作業,實際並沒有死,大部分原始是因為阻塞或者死鎖。

可以用jvisualvm,jmap等工具進行檢視

 

 

 

 

 

 

 

 

 

 

本文整理來源於:

《Java高併發程式設計詳解:多執行緒與架構設計》 --汪文君