JDK源碼分析 多線程

說明

對于JDK源碼分析的文章,僅僅記錄我認為重要的地方。源碼的細節實在太多,不可能面面俱到地寫清每個邏輯。所以我的JDK源碼分析,著重在JDK的體系架構層面,具體源碼可以參考:http://www.cnblogs.com/skywang12345/category/455711.html

實現多線程的兩種方式

  • Thread
  • Runnable

優先使用 Runnable

因為 Thread 是類,一個類只能有一個父類,但是可以有多個接口。Runnable 有更好的擴展性。

Runnable 還可以用于“資源的共享”,如果多個線程是基于同一個 Runnable 對象建立的,它們會共享 Runnable 對象上的資源。

start() 和 run()的區別說明

  • start: 啟動一個新的線程,新線程調用 run 方法。start 不能重復調用
  • run:和普通方法一樣,可以重復調用。但是并不會啟動新線程

測試代碼

public class jdk {
    public static class MyThread extends Thread {
        public MyThread(String name) {
            super(name);
        }
        
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " is running.");
        }
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread("myThread");
        
        System.out.println(Thread.currentThread().getName()+" call mythread.run()");
        thread.run();
        
        System.out.println(Thread.currentThread().getName()+" call mythread.start()");
        thread.start();
    }
}

輸出:

main call mythread.run()
main is running
main call mythread.start()
mythread is running

分析:

main中,直接調用thread.run()時,僅僅是調用了MyThread實例的run()方法,并沒有新建線程。所以run()方法中的Thread.currentThread().getName(),仍是main()!

而在調用thread.start()后,就會啟動新的MyThread線程,并調用其中的run()方法,此時Thread.currentThread().getName()為新啟動線程。

start 源碼

public synchronized void start() {
    // 如果線程不是"就緒狀態",則拋出異常!
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    // 將線程添加到ThreadGroup中
    group.add(this);
    boolean started = false;
    try {
        // 通過start0()啟動線程
        start0();
        // 設置started標記
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

run 源碼

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

wait(), notify(), notifyAll()

  • wait():讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
  • wait(long timeout):讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
  • notify():喚醒在此對象監視器上等待的單個線程
  • notifyAll():喚醒在此對象監視器上等待的所有線程

notify()是依據什么喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什么關聯起來的?

答案是:依據“對象的同步鎖”。

多線程循環打印 ABC

思路:考察多線程間通信。首先需要一個線程共有的變量,來標識打印的狀態。在 run 方法中,首先獲取一個公共的對象,相當于是把「多線程變成順序執行」,如果獲取鎖后,并不是需要打印的狀態,就釋放鎖,進入等待;其它線程會得到鎖,然后判斷,如果是打印狀態,就打印,然后通知所有線程,并釋放鎖。

public class PrintABC {
    private static final int PRINT_A = 0;
    private static final int PRINT_B = 1;
    private static final int PRINT_C = 2;
    
    private static class MyThread extends Thread {
        int which; // 0:打印A;1:打印B;2:打印C
        static volatile int state; // 線程共有,判斷所有的打印狀態
        static final Object t = new Object();
        
        public MyThread(int which) {
            this.which = which;
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                synchronized (t) {
                    while (state % 3 != which) {
                        try {
                            t.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.print(toABC(which)); // 執行到這里,表明滿足條件,打印
                    state++;
                    t.notifyAll(); // 調用notifyAll方法
                }
            }
        }
    }
    
    public static void main(String[] args) {
        new MyThread(PRINT_A).start();
        new MyThread(PRINT_B).start();
        new MyThread(PRINT_C).start();
    }
    private static char toABC(int which) {
        return (char) ('A' + which);
    }
}

yield

不會釋放鎖

sleep

讓當前線程休眠,由“運行狀態”→“阻塞狀態”。并不會釋放任何鎖,到時間后線程會進入“就緒狀態”。

測試代碼

// SleepLockTest.java的源碼
public class SleepLockTest{ 
    private static Object obj = new Object();
    public static void main(String[] args){ 
        ThreadA t1 = new ThreadA("t1"); 
        ThreadA t2 = new ThreadA("t2"); 
        t1.start(); 
        t2.start();
    } 
    static class ThreadA extends Thread{
        public ThreadA(String name){ 
            super(name); 
        } 
        public void run(){ 
            // 獲取obj對象的同步鎖
            synchronized (obj) {
                try {
                    for(int i=0; i <10; i++){ 
                        System.out.printf("%s: %d\n", this.getName(), i); 
                        // i能被4整除時,休眠100毫秒
                        if (i%4 == 0)
                            Thread.sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } 
    } 
}

輸出的結果(t1 和 t2 的順序不定,但是必定是同時執行0~9,因為 sleep 并沒有釋放鎖):

t1: 0
t1: 1
t1: 2
t1: 3
t1: 4
t1: 5
t1: 6
t1: 7
t1: 8
t1: 9
t2: 0
t2: 1
t2: 2
t2: 3
t2: 4
t2: 5
t2: 6
t2: 7
t2: 8
t2: 9

join

作用

讓“主線程”等待“子線程”結束之后才能繼續運行。

// 主線程
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 子線程運行結束后,再繼續運行。

源碼

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        while (isAlive()) { // isAlive() 通過子線程調用,則判斷的就是子線程
            wait(0); // 等待的是當前線程(CPU 正則運行的線程),而不是子線程!
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

測試代碼

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread start ...");
        for (int i = 0; i < 1000000; i++) {
            
        }
        System.out.println("MyThread end ...");
    }
}

@Test
public void testJoin() throws InterruptedException {
    Thread thread = new MyThread();
    thread.start();
    thread.join();
    System.out.println("testJoin end ...");
}

輸出:

MyThread start ...
MyThread end ...
testJoin end ...

我們可以看到,調用了 join() 后,主線程輸出的testJoin end ...是在MyThread end ...之后,這是因為main線程等待thread線程執行結束后,才繼續執行。

問題:為什么在main中調用的thread.join(),卻是main線程等待?

雖然是調用子線程的wait()方法,但是它是通過“主線程”去調用的;所以,休眠的是主線程,而不是“子線程”!

wait() 源碼的注釋:Causes the current thread to wait

用戶線程 守護線程

  • 用戶線程:用戶執行用戶級任務
  • 守護線程:“后臺線程”,一般用來執行后臺任務。

需要注意的是:Java虛擬機在“用戶線程”都結束后會后退出。

問題:main() 方法,是守護線程?

答案:不是。main()方法啟動的是非守護線程,如果只有這個線程,在mian()的最后一行代碼,JVM 會退出(因為所有非守護進程都死了)。

驗證代碼:

public static void main(String[] args){ 
    System.out.println(Thread.currentThread().getName());
    System.out.println(Thread.currentThread().isDaemon());
} 

輸出:

main
false

JVM終止運行條件

當Java虛擬機啟動時,通常有一個單一的非守護線程(該線程通過是通過main()方法啟動)。JVM會一直運行直到下面的任意一個條件發生,JVM就會終止運行:

  1. 調用了exit()方法,并且exit()有權限被正常執行。
  2. 所有的“非守護線程”都死了(即JVM中僅僅只有“守護線程”)。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,503評論 1 15
  • 該文章轉自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,381評論 3 87
  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 2,987評論 1 18
  • 寫在前面的話: 這篇博客是我從這里“轉載”的,為什么轉載兩個字加“”呢?因為這絕不是簡單的復制粘貼,我花了五六個小...
    SmartSean閱讀 4,792評論 12 45
  • 時間過去2個月了、該放下的還是放下吧。自己努力了,也爭取了、不留遺憾就好。 社會現實。多努力、??、??、你想要的也是...
    devleoper_rui閱讀 188評論 0 0