多線程之線程狀態,狀態切換種類及代碼實例


線程的六種基本狀態為:

1.NEW(剛新建)

2.Runable(可運行)

3.Blocked(被阻塞)

4.Waiting ( 等待 )

5.Timed waiting (計時等待)

6.Terminated (被終止,即執行完畢或線程死亡)

 

以上為線程調度的基本知識需求,接下來進入線程的各個狀態的流程細節。

 

  線程執行實例:單線程,直接不中斷執行,直至執行完畢

public class Demo1 {

    static class Thread1 extends Thread{

        @Override
        public void run() {
            int i=0;
            while (i++<10){
                System.out.println("now i = "+i);
            }
        }
    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        t1.start();

    }
    
}

輸出為:

now i = 1
now i = 2
now i = 3
now i = 4
now i = 5
now i = 6
now i = 7
now i = 8
now i = 9
now i = 10

Process finished with exit code 0

 

這是一個基本的線程執行,可以說是最最最最簡單的線程執行,它沒有線程切換的過程。

那么線程的切換有哪幾種呢?

 

  首先直接上狀態和狀態切換總覽圖

圖看不清,可以在新網頁中打開,目前沒有找到好辦法能讓這么大內容的圖

 

二、線程切換的種類

從上圖看,有三種:

第一種:由鎖競爭帶來的線程阻塞,如何重新啟動被阻塞的線程

第二種:在線程進入等待狀態后,如何喚醒該線程

第三種:在線程線程休眠時,如何重新喚醒該線程

 

其中,

第一種是在多條線程競爭鎖產生的,是由於鎖的機制而導致的線程問題,

第二種和第三種是對某條線程獨立進行的控制,是程序員自己主動控制的。


總結如下

 圖看不清,可以在新網頁中打開,目前沒有找到好辦法能讓這么大內容的圖

三、從運行如何切換到各種阻塞類型

3.1  鎖等待狀態如何產生,如何解決鎖等待問題?

首先要知道鎖競爭和控制方式,首先需要知道什么是鎖.

  什么是鎖?鎖用來標記一段代碼或方法,讓這段代碼在某個時間段只能有單獨一個線程訪問的工具。簡單來說,就是線程拿到了鎖就能執行下去,沒有拿到鎖,就只能繼續等待。

  一般來說,基本的鎖分為兩種,對象鎖和類鎖。每個對象實例擁有一把對象鎖,但多個對象實例對應的類只有一把類鎖,無論有多少對象實例都共享同一把類鎖,這兩點是鎖控制機制的基礎,具體的類鎖和對象鎖,乃至於重入鎖,條件鎖,讀寫鎖等更多的鎖知識這里就不提及了,本文着重講如何改變線程的運行狀態。

下例中,因為無法獲取對象鎖,線程被阻塞的情況

package thread.stateChange;


public class Demo2 {

    static class Thread1 extends Thread{

        static final Object lock = 0;  //由於以同一個線程類來創建線程實例,所以使用靜態對象來提供對象鎖
        String str = "";

        public Thread1(String str) {
            this.str = str;
        }

        @Override
        public void run() {
            int i=0;
            Print(str);
        }

        public void Print(String str){
            synchronized (lock) {
                while (true){
                    System.out.println(str);
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1("線程1");
        Thread1 t2 = new Thread1("線程2");
        t1.start();
        try {
            //同時啟動線程並不能保證線程1先進入運行狀態,所以暫停1s保證線程1比線程2先進入運行狀態
            Thread.sleep(1000);
            t2.start();

            while (true){
                Thread.sleep(1000);
                System.out.println("t1 state:"+t1.getState());
                System.out.println("t2 state:"+t2.getState());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

打印結果:

線程1
線程1
t1 state:TIMED_WAITING
t2 state:BLOCKED
線程1
t1 state:TIMED_WAITING
t2 state:BLOCKED
線程1
t1 state:TIMED_WAITING
t2 state:BLOCKED
線程1

Process finished with exit code -1

顯然線程2被阻塞了。

  由於synchronized關鍵字會給Print方法加上對象鎖,進入之后會進入死循環,一直打印,不會釋放掉鎖。

  所以線程2會一直等待該鎖的資源,狀態一直顯示為BLOCKED,而已經獲取鎖對象的線程1則一直可以執行,之所以顯示的是TIMED_WAITING,是為了降低輸出速度,在代碼里設置了打印一次休眠一秒鍾,在休眠是打印狀態,自然對應的是TIMED_WAITING,而不是RUNABLE。

  注:這里有一個知識點,sleep方法會讓出CPU的時間片資源,但是不會釋放掉獲取的鎖,wait方法則是讓出CPU時間片資源並且釋放掉對象的鎖。

 

現在要解決的問題是,如何讓線程2也能執行呢?

 1.等待線程1執行完畢后釋放鎖,線程2即可繼續執行

 2.中斷線程1的執行,釋放鎖,讓位給線程2執行

 

  方法1是一個很平常的順序等待執行邏輯,着重點在於讓執行中的線程中斷,讓位給其他線程的情況。

 

  下面是方法2的示例代碼:

package thread.stateChange;


public class Demo2 {

    static class Thread1 extends Thread{

        static final Object lock = 0;  //由於以同一個線程類來創建線程實例,所以使用靜態對象來提供對象鎖
        String str = "";

        public Thread1(String str) {
            this.str = str;
        }

        @Override
        public void run() {
            int i=0;
            Print(str);
        }

        public void Print(String str){
            synchronized (lock) {
                while (true){
                    System.out.println(str);
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        System.out.println(e.toString());   //打印中斷異常,並停止線程
                        break;
                    }
                }
            }
        }

    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1("線程1");
        Thread1 t2 = new Thread1("線程2");
        t1.start();
        try {
            //同時啟動線程並不能保證線程1先進入運行狀態,所以暫停1s保證線程1比線程2先進入運行狀態
            Thread.sleep(1000);
            t2.start();


            int i=0;
            while (true){
                //打印兩次之后再中斷線程1
                if (i++> 2){
                    t1.interrupt();
                    i = -100000000;
                }
                Thread.sleep(1000);
                System.out.println("t1 state:"+t1.getState());
                System.out.println("t2 state:"+t2.getState());

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

    }

}

 

輸出結果為:

線程1
線程1
線程1
t1 state:TIMED_WAITING
t2 state:BLOCKED
線程1
t1 state:TIMED_WAITING
t2 state:BLOCKED
線程1
t1 state:TIMED_WAITING
t2 state:BLOCKED
java.lang.InterruptedException: sleep interrupted
線程2
線程2
t1 state:TERMINATED
t2 state:TIMED_WAITING
線程2
t1 state:TERMINATED
t2 state:TIMED_WAITING

Process finished with exit code -1

 

顯然線程1被打算之后,線程2獲得了鎖於是進入執行狀態。

這里有幾個關鍵點:

1.調用線程對象的interupt方法,只是更改中斷信號的布爾值,並不對線程的執行造成影響,檢測方法卻有

isInterrupted,interrupted ,其中interrupted會重置中斷狀態的標志布爾值,isInterrupted不會。

2.中斷信號的處理,必須自己在定義線程執行內容的時候定義好,也就是說,只能某個線程自己中斷自己的執行,外部只能提供中斷信號。若沒有提供中斷信號的處理邏輯,則線程會繼續執行下去。

3.interupt方法,在線程處於wait,sleep,jion狀態時,如果執行會拋出中斷異常。

總結:如果想要讓一個執行中的線程中止,並釋放鎖,可以調用interupt方法給運行中的線程發送中斷信號,線程本身在執行中需要處理中斷異常,否則會造成程序終止。

 

3.2 線程如何從運行中進入等待狀態,如何從等待狀態喚醒該線程?

進入等待狀態:在同步代碼塊或同步方法里,調用wait方法

喚醒等待線程:在同步代碼塊或方法里,調用notify方法

注:為什么要在同步區域里操作呢?

  因為wait隱含了一個釋放鎖的操作,那么首先需要持有一把鎖,否則沒有鎖能被釋放。而喚醒需要鎖競爭的線程,首先也需要持有鎖才能讓其余線程來競爭鎖。

  原本鎖是用來處理多線程爭搶某個資源的,但是這里是對一條線程進行狀態切換,是不同的。真實的多線程會有許多的線程,同時進入這個方法,然后處於wait狀態,都在等待獲取鎖,並處於wait狀態,即使沒有任何對象持有鎖,線程也無法主動去獲取鎖,繼續下去,只有當受到notify信號激活所有等待的線程的信號后,程序才會重新執行起來。

 

上一段代碼示例:

package thread.stateChange;


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo4 {

    static class Printer {

        public  void Print(String str) {
            synchronized (this) {
                System.out.println(str);
                try {
                    System.out.println(str +" and enter wait state!" );
                    wait();         //進入無限等待狀態
                    //等待被喚醒后,繼續執行
                    int i = 0;
                    while (i++ < 3) {
                        Thread.currentThread().sleep(1000);
                        System.out.println(str);
                    }
                } catch (InterruptedException e) {
                    System.out.println(e.toString());   //打印中斷異常,並停止線程
                }
            }
        }

        public void wakeup() {
            synchronized (this) {
                notify();   //喚醒該線程
            }
        }

    }

    public static void main(String[] args) {
        Printer p = new Printer();
        Runnable r1 = () -> p.Print("this is r1!");
        Runnable r2 = () -> p.Print("this is r2!");
        Runnable r3 = () -> p.Print("this is r3!");
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,3,30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(30));
        executor.execute(r1);
        executor.execute(r2);
        executor.execute(r3);

        try {
            Thread.sleep(2000);
            p.wakeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

輸出結果為:

this is r2!
this is r2! and enter wait state!
this is r1!
this is r1! and enter wait state!
this is r3!
this is r3! and enter wait state!
this is r2!
this is r2!
this is r2!

Process finished with exit code -1

 

顯然,這里僅僅喚醒了一條線程。將wakeup換成notifyAll方法,再運行一下,輸出結果為:

this is r1!
this is r1! and enter wait state!
this is r3!
this is r3! and enter wait state!
this is r2!
this is r2! and enter wait state!
this is r2!
this is r2!
this is r2!
this is r3!
this is r3!
this is r3!
this is r1!
this is r1!
this is r1!

Process finished with exit code -1

這樣,就將所有進入了等待狀態的線程都喚醒了。

  但是需要特別說明的是,喚醒過程中,是讓三條需要競爭鎖的線程來重新爭奪鎖,所以誰先搶到,就是誰先執行,運行起來會有順序的不同是正常的。

  如果在wait方法后,不結束方法調用,即永遠不釋放鎖,那么其余線程都得不到運行機會。

 

  但是在wait方法的源碼里,有這樣的說明:

/**
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object.
     * In other words, this method behaves exactly as if it simply
     * performs the call {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until another thread * notifies threads waiting on this object's monitor to wake up
     * either through a call to the {@code notify} method or the
     * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }

 

上面的說明,簡單說了兩點:

1.拿到對象鎖的線程才能調用該方法

2.存在中斷和虛假喚醒,所以應當放在循環中使用

 

對於第一點,獲取對象鎖的方法在源碼里存在說明:

     * This method should only be called by a thread that is the owner
     * of this object's monitor. A thread becomes the owner of the
     * object's monitor in one of three ways:
     * <ul>
     * <li>By executing a synchronized instance method of that object.
     * <li>By executing the body of a {@code synchronized} statement
     *     that synchronizes on the object.
     * <li>For objects of type {@code Class,} by executing a
     *     synchronized static method of that class.
     * </ul>

翻譯過來就是:

1。執行對象的同步方法

2。執行對象發同步代碼塊

3。執行靜態同步方法

這些個都好懂,基本是復習一下。

 

但是虛假喚醒是什么東西?

上個代碼說明問題:

 

package thread.stateChange;


public class Demo6 {

    static class Thread1 {
        volatile int num = 3;

        public synchronized int add() {
            try {
                if (num >= 5) {
                    wait();
                }
                num++;
                System.out.println("add : " + num);
                notifyAll();
                return num;

            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
        }

        public synchronized int minus() {
            try {
                if (num <= 0) {
                    wait();
                }

                num--;
                System.out.println("minus : " + num);

            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
        }
    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        Thread t2 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.add();
            }
        });
        Thread t5 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.add();
            }
        });
        Thread t3 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.minus();
            }
        });
        Thread t4 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.minus();
            }
        });
        t2.start();
        t3.start();
        t4.start();
        t5.start();


    }

}

 

輸出的結果為:

minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
minus : -1
add : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3

Process finished with exit code 0

  顯然上面的結果,輸出了負數,也就是說,我們設置的數字范圍0到5失效。

  要出現這樣的結果應該是這樣一個過程:

   1.一個減法線程進入發現值為0,進入等待狀態,

   2.然后一個加法進入,符合條件執行加一操作,但在這個加法線程操作的同一時刻,另一個減法線程也進入發現值為0,也進入等待狀態

   3.等加法線程執行加一操作完畢之后,通知等待的減法線程可以操作了,不巧的是有兩條減法線程在等待,而加法又只是從0加一使得num變成了1,這樣一來,兩條減法線程都得到通知可以執行減法操作了,於是都執行-1操作,導致num的值出現-1這樣超出期望的值。

 

所以,虛假喚醒,就是喚醒了原本並不應喚醒的線程,造成了超出預期的結果,而原本我們編寫代碼的時候並不想喚醒某個線程或者並不想這樣喚醒某個線程。

如何解決呢?

源碼里面給出了建議就是在循環中使用判斷,改進后應該如下所示:

 

package thread.stateChange;


public class Demo6 {

    static class Thread1 {
        volatile int num = 3;

        public synchronized int add() {
            try {
                if (num >= 5) {
                    wait();
                }
                num++;
                System.out.println("add : " + num);
                notifyAll();
                return num;

            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
        }

        public synchronized int minus() {
            try {
                while (num <= 0) { wait(); }

                num--;
                System.out.println("minus : " + num);

            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
        }
    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        Thread t2 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.add();
            }
        });
        Thread t5 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.add();
            }
        });
        Thread t3 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.minus();
            }
        });
        Thread t4 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.minus();
            }
        });
        t2.start();
        t3.start();
        t4.start();
        t5.start();


    }

}

相比於之前會出現問題的代碼,僅僅改動了紅色部分,使用循環判斷等待條件,現在輸出為:

minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3

Process finished with exit code 0

 

一切正常了!

翻到上文,wait方法源碼上面的示例,就是這樣處理的,這也是為什么需要這樣處理的原因。

 

這里有一個問題,nitofyAll顯然是通知所有的線程,我們只是加一,能否只通過notify方法通知一個線程就好呢?

這樣就能避免通知多線程而出現問題呢?

將NotifyAll改成notify之后,輸出為:

minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
add : 6
add : 7
add : 8
add : 9
add : 10
add : 11
add : 12
add : 13
add : 14
add : 15
add : 16
add : 17
add : 18
add : 19
add : 20
add : 21
add : 22
add : 23
add : 24
add : 25
add : 26
add : 27
add : 28
add : 29
add : 30
add : 31
add : 32
add : 33
add : 34
add : 35
add : 36
add : 37
add : 38
add : 39
add : 40
add : 41
add : 42
add : 43
add : 44
add : 45
add : 46
add : 47
add : 48
add : 49
add : 50
add : 51
add : 52
add : 53
add : 54
add : 55
add : 56
add : 57
add : 58
add : 59
add : 60
add : 61
add : 62
add : 63
add : 64
add : 65
add : 66
add : 67
add : 68
add : 69
add : 70
add : 71
add : 72
add : 73
add : 74
add : 75
add : 76
add : 77
add : 78
add : 79
add : 80
add : 81
add : 82
add : 83
add : 84
add : 85
add : 86
minus : 85
minus : 84
minus : 83
minus : 82
minus : 81
minus : 80
minus : 79
minus : 78
minus : 77
minus : 76
minus : 75
minus : 74
minus : 73
minus : 72
minus : 71
minus : 70
minus : 69
minus : 68
minus : 67
minus : 66
minus : 65
minus : 64
minus : 63
minus : 62
minus : 61
minus : 60
minus : 59
minus : 58
minus : 57
minus : 56
minus : 55
minus : 54
minus : 53
minus : 52
minus : 51
minus : 50
minus : 49
minus : 48
minus : 47
minus : 46
minus : 45
minus : 44
minus : 43
minus : 42
minus : 41
minus : 40
minus : 39
minus : 38
minus : 37
minus : 36
minus : 35
minus : 34
minus : 33
minus : 32
minus : 31
minus : 30
minus : 29
minus : 28
minus : 27
minus : 26
minus : 25
minus : 24
minus : 23
minus : 22
minus : 21
minus : 20
minus : 19
minus : 18
minus : 17
minus : 16
minus : 15
minus : 14
minus : 13
minus : 12
minus : 11
minus : 10
minus : 9
minus : 8
minus : 7
minus : 6
minus : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0

這樣的輸出是一個很奇葩的結果,但是為什么呢?

  因為多線程的CPU競爭資源機制和notify的效果。首先,多個線程對CPU的競爭結果並沒有穩定的結果預期,notify的確能通知其余的線程開始搶占資源,但是在等待競爭的線程到底誰會獲得鎖並獲取資源是完全沒有預期的。

  問題1:上面的結果,相當明顯的是全都超過了5,為什么還能一直往上加呢?

  因為即使一個加法線程執行時,另外一個加法線程和兩個減法線程都在等待狀態,但是notify通知的時候,恰恰是加法線程獲得了鎖,於是又再次開始加1。於此同時,另外一個剛做完加法的線程也進入等待狀態,並在下一次鎖競爭中獲勝,於是又執行+1,於此同時,剛做完加法的線程又來開始爭奪鎖,並獲勝····一直循環,就造成了明明早就越過邊界條件,但是一直在執行+1的荒謬情況。

  改進一下,在加法線程中,也用while循環來判斷條件

public synchronized int add() {
            try {
                while (num >= 5) {
                    wait();
                }
                num++;
                System.out.println("add : " + num);
                notify();
                return num;

            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
        }

 

輸出結果成為:

minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0

  的確是不超過5了,但是有一個更嚴重的問題:我們四條線程會一共操作200次,但這里的輸出遠遠少於200次,程序也並沒有輸出終止符,表示程序依然活着,但卻沒有做任何事。

  結合代碼思考一下,為什么會出現程序卡住?

  1.要么因為synchronized而出現死鎖

  2.要么所有的狀態都進入wait狀態了。

 檢測一下,加一段輸出線程狀態的代碼

 

package thread.stateChange;


public class Demo6 {

    static class Thread1 {
        volatile int num = 3;

        public synchronized int add() {
            try {
                while (num >= 5) {
                    wait();
                }
                num++;
                System.out.println("add : " + num);
                notify();
                return num;

            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
        }

        public synchronized int minus() {
            try {
                while (num <= 0) {
                    wait();
                }
                num--;
                System.out.println("minus : " + num);

            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
        }
    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1();
        Thread t2 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.add();
            }
        });
        Thread t5 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.add();
            }
        });
        Thread t3 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.minus();
            }
        });
        Thread t4 = new Thread(() -> {
            int i = 0;
            while (i++ < 50) {
                t1.minus();
            }
        });
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        // 打印狀態的線程
        Thread t6 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        // 1秒打印一次,不然瘋狂打印,根本看不到其余的輸出
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("加法線程1"+t2.getState());
                    System.out.println("加法線程2"+t3.getState());
                    System.out.println("減法線程1"+t4.getState());
                    System.out.println("減法線程2"+t5.getState());
                }
            }
        });
        t6.start();

    }

}

輸出結果為:

add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
加法線程1WAITING
加法線程2WAITING
減法線程1WAITING
減法線程2WAITING
加法線程1WAITING
加法線程2WAITING
減法線程1WAITING
減法線程2WAITING
加法線程1WAITING
加法線程2WAITING
減法線程1WAITING
減法線程2WAITING
加法線程1WAITING
加法線程2WAITING
減法線程1WAITING
減法線程2WAITING

  顯然,是因為所有的線程都進入了等待狀態,而不是因為死鎖,因為死鎖的話線程的狀態是blocked。

  這樣的群體沉睡是如何發生的呢?

  查看輸出,我們會發現,程序停在了num為0的狀態,並且前面全在執行減法操作,那么就是在執行減法操作之前,加法線程全在都在沉睡狀態,但是當num被減到0的時刻,又是減法拿到了鎖,加法仍舊在沉睡,但是num為0,減法又進入沉睡了,至此,所有線程都進入沉睡,群體GG。

  上面導致全體進入沉睡的代碼,只有加法有notify操作,減法沒有通知操作,那么給減法加上呢?

public synchronized int minus() {
            try {
                while (num <= 0) {
                    wait();
                }
                num--;
                System.out.println("minus : " + num);
                notify();
            } catch (InterruptedException e) {
                System.out.println(e.toString());
            }
            return num;
}

輸出變為:

minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3
add : 4
add : 5
minus : 4
minus : 3
minus : 2
minus : 1
minus : 0
add : 1
add : 2
add : 3

Process finished with exit code 0

  程序輸出符合預期,並且打印終止字符串。

  之所以是這樣的情況,是因為notify和線程的執行次數都是200,所以能夠保證一個notify推動一條線程執行一次,直接的結果就是所有的線程的都會得到通知,等於加notifyAll方法的效果。

  notifyAll不僅僅通知減法線程,連加法線程也會得到通知。

 

虛假喚醒的總結就是:

  要使用while循環來反復驗證條件變量是否符合要求,避免因為鎖爭奪的不確定結果影響了代碼的預期效果,其發生的本質原因就是喚醒操作並沒有確定的喚醒線程對象,鎖爭奪的勝利者是所有鎖等待線程中隨機產生的。

 

 

3.3 如何讓線程進入睡眠狀態,又如何將線程從睡眠狀態中喚醒呢?

如何進入睡眠狀態:顯然是sleep方法,時間參數決定睡眠多久。

從睡眠狀態喚醒:一般等待休眠時間結束,要想提前喚醒線程,可以使用interupt方法,但會拋出中斷異常需處理。

上代碼:

package thread.stateChange;


public class Demo5 {

    static class Thread1 extends Thread {

        static final Object lock = new Object();  //由於以同一個線程類來創建線程實例,所以使用靜態對象來提供對象鎖
        String str = "";

        public Thread1(String str) {
            this.str = str;
        }

        @Override
        public void run() {
            int i = 0;
            Print(str);
        }

        public void Print(String str) {
            System.out.println(str);
            try {
                sleep(10000000);    //沉睡超長時間,模擬永遠沉睡
                System.out.println("沉睡被打斷之后,並不會執行sleep之后的代碼!");
            } catch (InterruptedException e) {
                System.out.println(e.toString());   //打印中斷異常,並停止線程
            }
            System.out.println("但是會執行異常捕捉模塊和捕捉代碼塊之后的代碼!");

        }


    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1("線程1");
        t1.start();
        try {
            Thread.sleep(1000);

            int i = 0;
            while (true) {
                System.out.println("t1 state:" + t1.getState());
                //打印兩次之后再喚醒線程1
                if (i++ > 2) {
                    t1.interrupt();
                    i = -100000000;
                }
                Thread.sleep(1000);

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

    }

}

 

輸出結果為:

 

線程1
t1 state:TIMED_WAITING
t1 state:TIMED_WAITING
t1 state:TIMED_WAITING
t1 state:TIMED_WAITING
java.lang.InterruptedException: sleep interrupted
但是會執行異常捕捉模塊和捕捉代碼塊之后的代碼!
t1 state:TERMINATED
t1 state:TERMINATED
t1 state:TERMINATED
t1 state:TERMINATED
t1 state:TERMINATED
t1 state:TERMINATED

Process finished with exit code -1

  顯然,sleep之后的代碼,在沉睡時間到之前被打斷,就不會執行。並且打斷沉睡會拋出中斷異常,需要處理這個異常。

 

  以上就是一條線程的狀態切換操作,純是個人基於嘗試和瞧源碼,搜博客整理而來,如有任何錯誤或不同意見,歡迎留言交流,共同進步。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM