多線程的常用操作方法sleep() yield() join() interrupt()

線程命名與獲取

多線程的運行狀態是不確定的,所以對於多線程操作必須有一個明確標識出線程對象的信息,這個信息往往通過名稱來描述。在Thread類中提供有如下的線程名稱方法:

在這裏插入圖片描述
我們看一段線程命名的代碼:

public class MyRunnable implements Runnable  {
    public void run() {
        for(int i=0;i<3;i++)
        {
            System.out.println("當前線程:"+Thread.currentThread().getName()+" i="+i);
        }
    }

    public static void main(String[] args) {
        MyRunnable myThread=new MyRunnable();
        new Thread(myThread).start();//沒有設置名稱
        new Thread(myThread).start();//沒有設置名稱
        new Thread(myThread,"線程A").start();//設置名稱

    }
}

在這裏插入圖片描述
主方法本身就是一個線程,所有的線程都是通過主線程創建並啓動的。

實際上每當使用了java命令去解釋程序的時候,都表示啓動了一個新的JVM進程。而主方法只是這個進程上的一個線程而已。

現放一張圖,以便後面的理解。
在這裏插入圖片描述
這是一張線程的五種狀態,以及它們之間具體是如何從一個狀態轉換到另一個狀態的。

線程的休眠(sleep()方法)

線程休眠:是指讓線程暫緩執行,等到預計時間之後再繼續執行。
線程休眠的時候回交出CPU的使用權,讓CPU去執行其他的任務,但是要注意的是線程休眠的時候不會釋放鎖,也就是說,當線程持有某個對象的鎖,則即使是線程休眠,其他對象也是無法訪問這個對象的。

public static native void sleep(long millis) throws InterruptedException

示例:

public class MyRunnable implements Runnable  {
    public void run() {
        for(int i=0;i<100;i++)
        {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("當前線程:"+Thread.currentThread().getName()+" i="+i);
        }
    }

    public static void main(String[] args) {
        MyRunnable myThread=new MyRunnable();
        new Thread(myThread).start();
        new Thread(myThread).start();

    }
}

仔細觀察結果會發現,結果有了間隔性。

通過代碼觀察會錯誤的認爲這三個線程是同時休眠的,但是千萬要記住,所有的代碼是依次進入到run()方法中的。真正進入到方法的對象可能是多個,也可能是一個。進入代碼的順序可能有差異,但是總體的執行是併發執行。

線程讓步(yield()方法)

線程讓步:暫停當前正在執行的任務,執行其他任務,意思就是調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先級的線程有獲取CPU執行時間的機會。

注意,調用yield方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的

在這裏我要多說幾句,因爲這一點我自己開始就很難理解,首先我們明確一個事實,sleep()調用會使線程進入阻塞狀態,而yield()方法調用會使線程進入就緒狀態。這是因爲sleep方法交出CPU使用權,然後在休眠的這個時間段裏,什麼也不做,等到時間到了,在繼續。而yield方法交出CPU使用權以後,在這一時間段裏它一直在等待CPU的使用權,並且在yield方法調用時,交出CPU使用權的時間是不確定的,通俗的來講,其實就是sleep方法在休眠的時間段裏,如果說,有別的線程給它CPU的使用權,它是不會要的,因爲它的時間段沒有結束,所以它不算是就緒狀態,也並非運行狀態,而yield在這一時間段裏,如果別的線程給它CPU的使用權,它是會接受的,所以就進入了就緒狀態,其實從字面意思就可以理解了,一個是休眠,一個是讓步。

觀察yield()方法

public class MyRunnable implements Runnable  {
    public void run() {
        for(int i=0;i<3;i++)
        {
            Thread.yield();
            System.out.println("當前線程:"+Thread.currentThread().getName()+" i="+i);
        }
    }
    public static void main(String[] args) {
        MyRunnable myThread=new MyRunnable();
        new Thread(myThread,"線程A").start();
        new Thread(myThread,"線程B").start();
        new Thread(myThread,"線程C").start();
    }
}

join()方法

等待該線程終止。意思就是如果在主線程中調用該方法時就會讓主線程休眠,讓調用該方法的線程run方法先執行完畢之後在開始執行主線程。這裏不太好理解,看個例子就能有所明白了。

// 主線程
public class Father extends Thread {
    public void run() {
        Son s = new Son();
        s.start();
        s.join();
        ...
    }
}
// 子線程
public class Son extends Thread {
    public void run() {
        ...
    }
}

在上面的代碼中,有兩個線程,一個是Father,一個是Son,而Son是在Father中創建的,所有Son是Father的子線程,Father是主線程,現在Father中創建了一個子線程,然後通過s.start()啓動子線程,再通過s.join()讓主線程休眠,一直到子線程執行完畢後,再繼續執行主線程。

上面的例子比較泛,再看一個具體一些的,就能對比出來。

public class MyRunnable implements Runnable  {
    public void run() {
        try {
        System.out.println("主線程睡眠前時間");
        printTime();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        System.out.println("主線程睡眠後時間");
        printTime();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    public static void printTime() {
        Date date=new Date();
        DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=format.format(date);
        System.out.println(time);
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子線程A");
        thread.start();
        System.out.println(Thread.currentThread().getName());
        thread.join();
        System.out.println("代碼結束");
    }
}

上面這個例子,在main方法中可以看到創建並啓動了一個子線程A,此時還沒有使用join方法,所以主線程還沒有休眠所以會先打印main線程的名稱,之後在調用了join方法,此時主線程開始休眠直到子線程A執行完畢後,再繼續執行主線程,結果如下:

在這裏插入圖片描述

線程停止

多線程中有三種方式可以停止線程:

  • 設置標記位,可以使線程正常退出。
  • 使用stop方法強制使線程退出,但是該方法不太安全所以已經被廢棄了。
  • 使用Thread類中的一個 interrupt() 可以中斷線程。

使用標記位退出線程

public class MyRunnable implements Runnable  {
    private boolean flag=true;
    public void run() {
        int i=1;
        while(flag){
            try {
                Thread.sleep(1000);
            System.out.println("第"+i+"次執行"+Thread.currentThread().getName()+" i="+i);
            i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子線程A");
        thread.start();
        Thread.sleep(3000);
        myThread.setFlag(false);
        System.out.println("代碼結束!");
    }
}

使用stop方法使線程退出

 public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子線程A");
        thread.start();
        Thread.sleep(3000);
        myThread.setFlag(false);
        System.out.println("代碼結束!");
    }
}

使用stop方法強制退出線程,由於這種方法不安全所以已經被廢棄。

爲什麼說stop不安全?這是因爲stop會解除由線程獲取的所有鎖定,當在一個線程對象上調用stop()方法時,這個線程對象所運行的線程就會立即停止,假如一個線程正在執行:synchronized void { x = 3; y = 4;} 由於方法是同步的,多個線程訪問時總能保證x,y被同時賦值,而如果一個線程正在執行到x = 3;時,被調用了 stop()方法,即使在同步塊中,它也會馬上stop了,這樣就產生了不完整的殘廢數據。

使用Thread.interrupt()

public class MyRunnable implements Runnable  {
    private boolean flag=true;
    public void run() {
        int i=1;
        while(flag){
            try {
                Thread.sleep(1000);
              boolean bool=Thread.currentThread().isInterrupted();
           if(bool){
               System.out.println("非阻塞狀態下執行該操作。。。");
           }
           else{
               System.out.println("第"+i+"次執行"+Thread.currentThread().getName());
           i++;
           }
            } catch (InterruptedException e) {
                System.out.println("退出了");
            }
        }

    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子線程A");
        thread.start();
        Thread.sleep(3000);
        thread.interrupt();
        System.out.println("代碼結束!");

    }
}

在上面的代碼中,在非阻塞狀態下,bool值一直是false,所以會執行“第一次…第二次…”,如果收到中斷信號,bool會變成true,此時會輸出“非阻塞狀態下執行該操作。。。”,而且線程不會因此停止運行,因爲只是改變了一箇中斷信號。

其實interrupt() 方法只是改變中斷狀態而已,它不會中斷一個正在運行的線程。這一方法實際完成的是,給受阻塞的線程發出一箇中斷信號,這樣受阻線程就得以退出阻塞的狀態。
然而 interrupt() 方法並不會立即執行中斷操作;具體而言,這個方法只會給線程設置一個爲true的中斷標誌(中斷標誌只是一個布爾類型的變量),而設置之後,則根據線程當前的狀態進行不同的後續操作。如果,線程的當前狀態處於非阻塞狀態,那麼僅僅是線程的中斷標誌被修改爲true而已;如果線程的當前狀態處於阻塞狀態,那麼在將中斷標誌設置爲true後,還會有如下三種情況之一的操作:如果是 wait、sleep以及join 三個方法引起的阻塞,那麼會將線程的中斷標誌重新設置爲false,並拋出一個InterruptedException ;
如果在中斷時,線程正處於非阻塞狀態,則將中斷標誌修改爲true,而在此基礎上,一旦進入阻塞狀態,則按照阻塞狀態的情況來進行處理;例如,一個線程在運行狀態中,其中斷標誌被設置爲true之後,一旦線程調用了wait、join、sleep方法中的一種,立馬拋出一個InterruptedException,且中斷標誌被程序會自動清除,重新設置爲false。

通過上面的分析,我們可以總結,調用線程類的interrupted方法,其本質只是設置該線程的中斷標誌,將中斷標誌設置爲true,並根據線程狀態決定是否拋出異常。因此,通過interrupted方法真正實現線程的中斷原理是:開發人員根據中斷標誌的具體值,來決定如何退出線程。

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