Java線程之基本概念(線程狀態)

Thread中有很多方法都是native的,Thread實例化的時候,其實就只是簡單的設置了一些變量,比如線程組、優先級、Runnable等,這個時候線程是“NEW”狀態。然後調用start方法的時候,才真正執行動作,比如把線程放到線程組裏、執行nativeCreate,然後VM纔會執行run方法,線程處於“RUNNABLE”狀態。

ThreadGroup是爲了方便對Thread進行管理,ThreadGroup中可以有ThreadGroup和Thread,所以成樹形結構。

Thread中一些重要的方法,簡單說明一下:

run方法:是啓動線程後,被JVM調用的。我們出入的Runnable在這裏被調用

start方法:啓動線程

sleep系列方法:有三個,需要注意的是,它們都是類方法

public static void sleep(long millis) throws InterruptedException

public static void sleep(long millis, int nanos) throws InterruptedException

private static native void sleep(Object lock, long millis, int nanos) throws InterruptedException

這個方法可以讓當前線程休眠,其實是進入了是了“TIMED_WAITING”狀態。比如,我們在主線程中調用sleep方法(說明一下,主線程的工作也是運行在run方法內的,主線程中調用sleep方法,意思就是主線程的run方法中調用sleep方法),會讓主線程休眠,此時主線程不會放棄任何monitor lock。我們看sleep中的源碼,是獲取了當前線程中的lock變量,把它當作monitor lock,調用了native sleep。其它線程中調用sleep也是一樣的。

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的實現,其實是對Object wait方法的封裝,方便使用而已。設計很好,因爲join是屬於Thread實例(比如A)的方法,它在另一個線程中被調用,比如在主線程中調用,由於join的實現是把A的lock變量作爲monitor lock,然後調用了lock.wai()方法,這樣主線程就進入了“WAITING”狀態,如果是wait有傳參數,是進入“TIMED_WAITING”狀態,monitor lock被釋放了。join的註釋說,A線程結束的時候,會調用notifyAll方法(具體怎麼調用的目前不知道。。。),這樣主線程就會被喚醒繼續執行。

/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */

通過分析join我們知道,join給我們提供了一種讓一個線程先等待另一個線程執行完再繼續執行的機制。其實在Android 的HandlerThread 的getLooper方法,和join原理是一樣的,看看java線程同步粗淺學習

再囉嗦一下,join翻譯成中文的意思是加入,就好像,我們執行主線程,new了一個子線程A,調用了A的join方法,就把A加入到主線程中了,等A執行完,主線程繼續執行。這個只是感覺上的理解,具體原理是上面的講解。

yield方法:這個是個native 的static方法,讓步的意思

這個方法一般不會在實際開發中遇到,這個方法會讓當前線程讓出CPU的時間片,然後當前線程和其它線程又同時競爭CPU

 

Thread的狀態

上面這張圖很棒,把線程的各種狀態描述的很清楚。

具體可以看下面這篇文章線程的狀態轉換以及基本操作

Thread有六種狀態NEW、 RUNNABLE、 WAITING、 TIMEOUT_WAITING、 BLOCKED、 TERMINATED。

需要區分一下WAITING/TIMEOUT_WAITING和BLOCKED的區別:

WAITING:A thread that is waiting indefinitely for another thread to perform a particular action is in this state. 在這個狀態,一個線程等待另一個線程完成特定的動作,時間不確定。

BLOCKED:A thread that is blocked waiting for a monitor lock is in this state.在這個狀態,一個線程是被阻塞的等待一個監聽器鎖。

從兩者的定義我們就知道是不一樣的。但是,在實際看別人文章的時候,也會把等待叫成阻塞,具體得根據上下文區分。

 

sleep和wait有什麼異同呢?

我們嘗試總結一下:我理解sleep就是線程自己讓自己休眠一會超時醒來,wait是線程讓自己休眠需要等待其它線程喚醒。

不同點

(1)sleep是Thread靜態方法,wait是Object的實例方法

(2)sleep調用和鎖沒有關係,也不影響鎖;wait的調用需要線程獲取目標對象的鎖,調用後會釋放鎖

(3)醒來方式不同:sleep超時自己醒來就可以了;wait帶參數的也可以自己超時醒來,還可以其它線程通過notify/notifyAll喚醒自己,醒來後還得去競爭鎖。

相同點:

(1)都會讓線程進入WAITING/TIMEOUT_WAITING狀態

(2)都可以通過中斷醒來

 

Thread.sleep和Object.wait方法都可以讓線程處於waiting狀態。sleep方法是Thread的靜態方法,這種設計就是說明只能在當前線程調用,而不能在其它線程調用。Thread.sleep會讓當前線程休眠一段時間,讓線程處於TIMEOUT_WAITING狀態,其本身調用是不需要monitor lock的,如果實際開發中線程確實有監聽鎖,sleep也不會導致lock釋放;休眠的線程可以通過一段時間後或者中斷醒過了,中斷的時候會拋出InterruptedException清除中斷標示。wait是Object的對象方法,也就是說任何對象都有這個方法;每個對象都有鎖和等待集合(這個表述注意理解),這就引出了另一個概念監聽器(看2),擁有鎖和等待集合的對象我們有個專有名稱叫目標對象;wait的調用,線程首先要獲取目標對象上的鎖,通過synchronized關鍵字實現的,wait會讓線程進入waiting狀態,jvm會把當前線程放到wait set中,目標對象上的鎖釋放了;當前線程還可能有其它鎖,其它鎖是不會釋放的,只有調用了wait的目標對象上的鎖纔會釋放;wait的線程有幾種方式的喚醒notify/notifyAll、中斷、超時、僞裝喚醒(spurious wakeup)。

擁有鎖和等待集合的實體通常成爲監聽器。(Entities possessing both locks and wait sets are generally called monitors)

我們看看下面的代碼,線程A啓動的時候,獲取了監聽鎖lock,休眠1000毫秒,然後調用了lock.wait(),這樣線程A就處在了WAITING狀態,同時線程A被放到了等待集合中,lock鎖被釋放了。在線程A休眠的時候,線程B、C和D,都因爲lock鎖獲取不到而阻塞起來,進入LOCKED狀態,我理解應該有個集合是存放阻塞了的線程B、C、D的 。線程A釋放lock鎖後,理論上BCD線程都會公平的去競爭lock鎖的(其實是根據jvm的實現策略決定的),按照我電腦的執行結果來看,每次都是線程D獲得lock鎖,線程D進入 WAITING狀態,線程D放入wait set中,lock鎖被釋放了。B、C線程競爭鎖,C線程獲得lock鎖,調用了lock.notify()去喚醒在目標對象等待集合中等待的線程,wait set中A和D線程,按道理都有可能,我的電腦的執行解決是喚醒了A線程,A線程被喚醒後是需要再次獲取鎖的,獲取不到鎖,阻塞起來,放到阻塞集合中(這塊不知道理解的對不對呀)。C釋放lock鎖後,B和A競爭,B獲得,進入wait set,釋放鎖,A獲取鎖,執行完退出。最後的結果是,wait set中還有D和B在等待。

 

notifyAll看下面執行結果

package com.lzy.learnpro;

/**
 * @Author zhongyili
 * @Date 2020/5/29
 */
public class JavaCodeMain2 {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread threadA = new Thread(new WaitRunnable(lock), "Thread_A");
        Thread threadB = new Thread(new WaitRunnable(lock), "Thread_B");
        Thread threadC = new Thread(new NotifyRunnable(lock), "Thread_C");
        Thread threadD = new Thread(new WaitRunnable(lock), "Thread_D");

        threadA.start();
        threadB.start();
        threadC.start();
        threadD.start();
    }


    static class WaitRunnable implements Runnable {
        Object lock;
        WaitRunnable(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            synchronized(lock) {
                System.out.println(Thread.currentThread() + " get lock");
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread() + " wait release lock");
                    lock.wait();
                    System.out.println(Thread.currentThread() + " again get lock");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println(Thread.currentThread() + " release lock");
            }

        }
    }

    static class NotifyRunnable implements Runnable {
        Object lock;
        NotifyRunnable(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            synchronized(lock) {
                System.out.println(Thread.currentThread() + " get lock");

//                lock.notify();
                lock.notifyAll();
                try {

                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " release lock");
            }

        }
    }

}



//  調用lock.notify的執行結果
//        Thread[Thread_A,5,main] get lock
//        Thread[Thread_A,5,main] wait release lock
//        Thread[Thread_D,5,main] get lock
//        Thread[Thread_D,5,main] wait release lock
//        Thread[Thread_C,5,main] get lock
//        Thread[Thread_C,5,main] release lock
//        Thread[Thread_B,5,main] get lock
//        Thread[Thread_B,5,main] wait release lock
//        Thread[Thread_A,5,main] again get lock
//        Thread[Thread_A,5,main] release lock

//   調用lock.notifyAll()的執行結果

//        Thread[Thread_A,5,main] get lock
//        Thread[Thread_A,5,main] wait release lock
//        Thread[Thread_D,5,main] get lock
//        Thread[Thread_D,5,main] wait release lock
//        Thread[Thread_C,5,main] get lock
//        Thread[Thread_C,5,main] release lock
//        Thread[Thread_B,5,main] get lock
//        Thread[Thread_B,5,main] wait release lock
//        Thread[Thread_D,5,main] again get lock
//        Thread[Thread_D,5,main] release lock
//        Thread[Thread_A,5,main] again get lock
//        Thread[Thread_A,5,main] release lock




 

參考

Java併發 之 線程組 ThreadGroup 介紹

ThreadGroup

Java Thread 源碼解析

Java 淺析 Thread.join()

Thread 的join方法解釋

Thread中yield方法

多線程 Thread.yield 方法到底有什麼用?

【Java8源碼分析】線程-Thread類的全面剖析

Thread中斷和InterruptedException處理

JAVA 線程狀態 阻塞和等待 bloked 和 waiting 區別

Doug Lea 大神寫到文章,就是寫線程池的那位大神,重點研讀

Java併發結構

java.lang.Thread.sleep()方法和java.lang.Object.wait()方法之間的區別

 

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