java線程狀態變更及中斷實現

1 java創建線程的幾種方式

1.1 實現Runnable接口。

public class RunnableDemo implements Runnable{
    public static void main(String[] args) {
        //寫法1
        new Thread(new RunnableDemo(), "runnable線程1").start();

        //寫法2
        new Thread(new Runnable() {
            public void run() {
                System.out.println("當前線程:" + Thread.currentThread().getName());
            }
        }, "runnable線程2").start();
    }

    public void run() {
        System.out.println("當前線程:" + Thread.currentThread().getName());
    }
}

1.2 繼承Thread類,重寫run方法。

public class ThreadDemo extends Thread{
    public ThreadDemo(String name){
        super.setName(name);
    }
    public static void main(String[] args) {
        //寫法1:
        new ThreadDemo("thread線程1").start();
        //寫法二:
        new Thread("thread線程2"){
            @Override
            public void run() {
                System.out.println("當前線程:" + Thread.currentThread().getName());
            }
        }.start();
    }
    @Override
    public void run() {
        System.out.println("當前線程:" + Thread.currentThread().getName());
    }
}

其實Thread類,也是實現了Runnable接口,重寫了run方法,默認的run方法的邏輯:

public void run() {
   if (target != null) {
       target.run();
   }
}

也就是,如果有target,就調用target的run,如果沒有target,就什麼也不做(當然,如果重新了thread的run,就會調用子類的run)。這個target是第一種創建線程方式(實現Runnable接口)傳入的Runnable。

1.3 Callable/Future

帶返回值

1.4 ThreadPool

實際中不會手動new Thread,因爲手動創建的線程不可控,而是使用線程池

2 線程狀態變更

2.1 狀態變更圖

在這裏插入圖片描述

2.2 查看線程狀態

可以通過 jstack 命令查看線程的狀態,如,有如下程序:

public class ThreadStatusDemo implements Runnable{
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "線程1").start();

        new Thread(new Runnable() {
            public void run() {
                synchronized (this){
                    try {
                        wait(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "線程2").start();

        new Thread(new Runnable() {
            public void run() {
                synchronized (this){
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "線程3").start();

        new Thread(new Runnable() {
            public void run() {
                while (true){
                    System.out.println(123344);
                }
            }
        }, "線程4").start();

        ThreadStatusDemo demo = new ThreadStatusDemo();
        new Thread(demo, "線程5").start();

        new Thread(demo, "線程6").start();
    }


    public void run() {
        synchronized (this){
            try {
                Thread.sleep(10000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

啓動程序後,首先通過jps查看java進程端口號:

D:\projects\thread>jps
11424 Launcher
1332
10120 KotlinCompileDaemon
10172 Jps
10572 ThreadStatusDemo

再通過jstack查看如下:

D:\projects\thread>jstack 10572 
2019-12-01 14:35:52
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode):

"DestroyJavaVM" #20 prio=5 os_prio=0 tid=0x0000000002cae800 nid=0x38a4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"線程6" #19 prio=5 os_prio=0 tid=0x000000001ecc8800 nid=0x2650 waiting on condition [0x000000002018f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.bxp.ThreadStatusDemo.run(ThreadStatusDemo.java:57)
        - locked <0x00000006c1c06178> (a com.bxp.ThreadStatusDemo)
        at java.lang.Thread.run(Thread.java:748)

"線程5" #18 prio=5 os_prio=0 tid=0x000000001ecc8000 nid=0x3478 waiting for monitor entry [0x000000002008e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bxp.ThreadStatusDemo.run(ThreadStatusDemo.java:57)
        - waiting to lock <0x00000006c1c06178> (a com.bxp.ThreadStatusDemo)
        at java.lang.Thread.run(Thread.java:748)

"線程4" #17 prio=5 os_prio=0 tid=0x000000001ecc5000 nid=0x3ed4 runnable [0x000000001ff8e000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileOutputStream.writeBytes(Native Method)
        at java.io.FileOutputStream.write(FileOutputStream.java:326)
        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
        - locked <0x00000006c1c0c090> (a java.io.BufferedOutputStream)
        at java.io.PrintStream.write(PrintStream.java:482)
        - locked <0x00000006c1c0c070> (a java.io.PrintStream)
        at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
        at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
        - locked <0x00000006c1c0c030> (a java.io.OutputStreamWriter)
        at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
        at java.io.PrintStream.newLine(PrintStream.java:546)
        - eliminated <0x00000006c1c0c070> (a java.io.PrintStream)
        at java.io.PrintStream.println(PrintStream.java:737)
        - locked <0x00000006c1c0c070> (a java.io.PrintStream)
        at com.bxp.ThreadStatusDemo$4.run(ThreadStatusDemo.java:42)
        at java.lang.Thread.run(Thread.java:748)

"線??3" #16 prio=5 os_prio=0 tid=0x000000001ecbf800 nid=0x1630 in Object.wait() [0x000000001fe8f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000006c1c06188> (a com.bxp.ThreadStatusDemo$3)
        at java.lang.Object.wait(Object.java:502)
        at com.bxp.ThreadStatusDemo$3.run(ThreadStatusDemo.java:31)
        - locked <0x00000006c1c06188> (a com.bxp.ThreadStatusDemo$3)
        at java.lang.Thread.run(Thread.java:748)

"線程2" #15 prio=5 os_prio=0 tid=0x000000001ecbe800 nid=0x1ee8 in Object.wait() [0x000000001fd8f000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000006c1c0a000> (a com.bxp.ThreadStatusDemo$2)
        at com.bxp.ThreadStatusDemo$2.run(ThreadStatusDemo.java:19)
        - locked <0x00000006c1c0a000> (a com.bxp.ThreadStatusDemo$2)
        at java.lang.Thread.run(Thread.java:748)

"線程1" #14 prio=5 os_prio=0 tid=0x000000001ecbd800 nid=0x250c waiting on condition [0x000000001fc8f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.bxp.ThreadStatusDemo$1.run(ThreadStatusDemo.java:8)
        at java.lang.Thread.run(Thread.java:748)

3 線程啓動,爲什麼調用的是start方法。

如果調用run方法,run方式只是一個普通的實例方法。我們並不是想簡單的執行run,而是希望能夠啓動一個線程,並且讓這個新啓動的線程去調用run。
start方法,作用就是創建一個線程,並且線程回調run方法。
可以跟一下Thread的start方法源碼,發現start中調用了start0,而start0是一個本地方法:

private native void start0();

我們可以下載hotspot源碼,查看start0的具體實現。可以通過Thread.c去找到java線程相關的本地方法和hotspot源碼的對應方法,如下:
在這裏插入圖片描述
從上圖中可以看到,start0對應的hotspot中的方法爲JVM_StartThread。JVM_StartThread方法在jvm.cpp文件中。
可以看到,JVM_StartThread中通過如下代碼創建了一個JavaThread

native_thread = new JavaThread(&thread_entry, sz);

而new JavaThread實現在thread.cpp中,如下:

os::create_thread(this, thr_type, stack_sz);

可以看出,是通過調用當前操作系統的方法,創建了一個線程,所以最終的創建線程,實際是通過操作系統來實現的。
創建好線程後,最後通過調用Thread::start啓動線程

Thread::start(native_thread);

Thread::start在thread.cpp中的具體實現如下:

void Thread::start(Thread* thread) {
  trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}

可以看出,是通過操作系統啓動了線程。並且在啓動前,設置了線程的狀態爲RUNNABLE。

4 爲什麼線程的stop等方法不建議使用?線程的終止方式?

stop方法,只需要持有線程的對象,就可以調用。也就是可以在任意線程中去終止一個線程,這就相當於在linux下通過kill -9 強制終止一個線程類似,對於線程來說是不安全的,因爲對於當前線程來說,被終止是不可知的。以及suspend(掛起),resume(恢復)等方法不建議使用也是同樣的。

4.1 通過自定義標誌位的方式終止線程

public class ThreadInterruptDemo {
    public static boolean isInterrupt = false;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!isInterrupt){
                    System.out.println("i = " + i ++);
                }
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isInterrupt = true;
    }
}

如上,通過控制isInterrupt 的值,可以達到終止線程的效果。

4.2 通過thread自帶的interrupt()的方式終止:

public class ThreadInterruptDemo2 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("i = " + i++);
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

這種方式,可以看到,其實思想和我們第一種方式自定義標誌位是類似的,只是這裏標誌位是線程定義,中斷方法也是線程自己提供的interrupt。 thread.interrupt(),就是把Thread.currentThread().isInterrupted()設置成true。可以看一下thread.interrupt()的實現,發現調用的是本地方法:

private native void interrupt0();

根據映射,發現實際調用的是hotspot中的JVM_Interrupt
在這裏插入圖片描述
繼續跟源碼,發現調用的是操作系統的interrupt:

void Thread::interrupt(Thread* thread) {
  trace("interrupt", thread);
  debug_only(check_for_dangling_thread_pointer(thread);)
  os::interrupt(thread);
}

以linux爲例,找一下源碼:
在這裏插入圖片描述
如上可以看到,最終就是設置interrupted的值爲true。
set_interrupted方法定義:

volatile jint _interrupted;     // Thread.isInterrupted state

void set_interrupted(bool z)                      { _interrupted = z ? 1 : 0; }

對於isInterrupted,同樣的方式,最終在hotspot中的實現:
在這裏插入圖片描述
interrupted()實現爲:

volatile bool interrupted() const                 { return _interrupted != 0; }

4.3 InterruptedException中斷

上面的兩種方式,都是在程序不斷的判斷標誌位的時候,通過控制標誌位來中斷線程,但是如果現在在執行過程中出現了wait,sleep,join的時候,我們該如何中斷線程呢?
這種情況下,也可以通過thread.interrupt();進行中斷,只是執行後並不會立即中斷線程的執行,而是拋出一箇中斷異常InterruptedException,收到這個中斷異常後,我們就可以在catch中自行處理了,此時是否中斷還是由當前線程說了算。
如下程序,執行後,會發現程序會繼續執行,並不會真正被中斷:

public class ThreadInterruptDemo4 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!Thread.currentThread().isInterrupted()){
                    synchronized (this){
                        try {
                            System.out.println("i = " + i);
                            wait();
                        } catch (InterruptedException e) {
                            System.out.println("接收到中斷異常");
                            e.printStackTrace();
                        }
                    }
                    i++;
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

上面程序拋出中斷異常信號,但是不會被真正中斷,只會拋出中斷異常信號,最終是否中斷,還是取決於線程本身,如果想中斷,可以再收到中斷異常後做相應的中斷處理。

5 線程的復位重置

5.1 通過Thread.interrupetd方法對線程進行復位

public class ThreadInterruptDemo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                while (true){
                    if (Thread.currentThread().isInterrupted()){
                        System.out.println("復位前:" + Thread.currentThread().isInterrupted());
                        Thread.interrupted();//復位
                        System.out.println("復位後:" + Thread.currentThread().isInterrupted());
                    }
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

從執行上面的代碼中,輸出true,false。中斷後,Thread.currentThread().isInterrupted()變爲true,通過Thread.interrupted()復位後,Thread.currentThread().isInterrupted()重新變爲false。

5.2 通過InterruptException復位

在線程的中斷方式中說過,處於阻塞狀態的線程,通過interrupt中斷,中斷後並不能立即終止線程,只是會拋出中斷異常,之所以沒有立即中斷,就是因爲在中斷後,又進行了復位操作。
可以看一下源碼:
在這裏插入圖片描述
上圖中,在sleep中,當同時滿足兩個條件的時候,拋出異常。
首先是Thread::is_interrupted (THREAD, true) == true,Thread::is_interrupted實現如下:

bool os::is_interrupted(Thread* thread, bool clear_interrupted) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");

  OSThread* osthread = thread->osthread();

  bool interrupted = osthread->interrupted();

  if (interrupted && clear_interrupted) {
    osthread->set_interrupted(false);
    // consider thread->_SleepEvent->reset() ... optional optimization
  }

  return interrupted;
}

首先獲得 osthread->interrupted(),因爲調用了interrupt觸發了中斷,所以此時osthread->interrupted()爲true,然後判斷如果interrupted == true並且clear_interrupted(這個應該是是否重置線程的標誌,傳入的爲true)爲true,然後重置了線程的狀態,變爲了false。最後返回true。

第二就是HAS_PENDING_EXCEPTION,字面意思是:是否有掛起的異常,這個變量在源碼中沒有找到在哪兒賦值的,暫且認爲和理解中斷無關,認爲是false吧。

這樣,那就是如果發現線程被中斷了,就會把線程狀態復位並且會拋出異常。

6 爲什麼所有的阻塞方法都會拋出InterruptException異常

因爲對於阻塞方法來說,如sleep, wait,join等,阻塞後,想要讓線程繼續執行,都有相對應的機制或者達到一定的條件,如sleep爲時間,wait和join爲notify或者notifyAll。而如果在阻塞中未達到條件的情況下,通過thread.interrupt進行中斷,對於線程本身來說是不正常的情況,所以這種情況下不會中斷線程,但是又不能什麼都不做,所以這個時候就會拋出中斷異常,線程本身收到中斷異常信號後,是否中斷由線程自身控制。

7 如果線程wait了,就會釋放CPU佔用,只有notify或者notifyAll的時候纔會重新搶佔CPU,但是調用interrupt的時候,線程會重新執行,所以interrupt中斷的時候,是否是喚醒了線程(線程處於sleep狀態下同樣)。

7.1 wait

先看一下interrupt的源碼如下:
在這裏插入圖片描述
如上圖,首先是設置線程中斷狀態爲true,然後通過unpark接觸了當前線程的阻塞狀態。

7.2 sleep

sleep不是阻塞,也沒有釋放cpu的佔用權,那sleep是怎麼做的呢,這就需要看一下sleep的實現:
在這裏插入圖片描述
如上圖,sleep原理大致就是一個死循環,然後對時間進行減減,當時間 <= 0的時候,就填出循環。當然,如果線程中斷狀態爲true的時候,也會跳出循環,根據前面講的,當跳出循環後,發現當前線程被中斷了,就會復位線程狀態並且拋出中斷異常。

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