java 併發編程——Thread 源碼重新學習



對於程序員來說 Thread應該都不會陌生,這裏再深入的去學習一下里面的很多借口

Thread的聲明如下:

class Thread implements Runnable

Runnable 接口是個什麼鬼?                                                                                                

複製代碼
public
interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
複製代碼

當開啓一個線程的時候,run()方法就會被執行在新開啓的線程。

ThreadGroup                                                                                                             

在瞭解Thread之前先了解一下ThreadGroup,結構其實很簡單如下圖(就是一個線程組,同時可以包含其它線程組。對於線程租的操作都是對裏面線程和子線程組的操作,而子線程組需要進行遞歸的操作):

未命名文件 (3)

當創建了好幾個線程的時候,很多線程的工作任務是類似或者一致的,這樣我們就可以使用ThreadGroup來管理它。

優點:方便統一管理,線程組可以進行復制,快速定位到一個線程,統一進行異常設置等。

大概看了裏面的接口設計,裏面的操作有一個特點:在同步塊中將子線程組拷貝到臨時變量中然後再在同步塊之外進行遞歸的操作。(這樣設計的優點和目的不知道是什麼,大神能夠解答的,麻煩留言。會不會有種可能:在list()的時候,同時在往線程組中添加子線程組)

複製代碼
  public void list() {
    list(System.out, 0);
}
void list(PrintStream out, int indent) {
    int ngroupsSnapshot;
    ThreadGroup[] groupsSnapshot;
    synchronized (this) {
        for (int j = 0 ; j < indent ; j++) {
            out.print("");
        }
        out.println(this);
        indent += 4;
        for (int i = 0 ; i < nthreads ; i++) {
            for (int j = 0 ; j < indent ; j++) {
                out.print("");
            }
            out.println(threads[i]);
        }
        ngroupsSnapshot = ngroups;
        if (groups != null) {
            
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
        } else {
            groupsSnapshot = null;
        }
    }
//同步塊中將 groups數組拷貝到臨時變量 groupsSnapshot中,再在同步塊之外對其進行遞歸操作。
    for (int i = 0 ; i < ngroupsSnapshot ; i++) {
        groupsSnapshot[i].list(out, indent);
    }
}
複製代碼

 

Thread的構造函數                                                                                                      


基本屬性

  • name:線程名稱,可以重複,若沒有指定會自動生成。
  • id:線程ID,一個正long值,創建線程時指定,終生不變,線程終結時ID可以複用。
  • priority:線程優先級,取值爲1到10,線程優先級越高,執行的可能越大,若運行環境不支持優先級分10級,如只支持5級,那麼設置5和設置6有可能是一樣的。
  • state:線程狀態,Thread.State枚舉類型,有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 5種。
  • ThreadGroup:所屬線程組,一個線程必然有所屬線程組。 RUNNABLE
  • UncaughtEThreadGroupxceptionHandler:未捕獲異常時的處理器,默認沒有,線程出現錯誤後會立即終止當前線程運行,並打印錯誤。

無參構造函數

 public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

設置Thread的name的構造函數

public Thread(String name) {
        init(null, null, name, 0);
    }

通過Runnable接口初始化一個Thread

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

上面幾種Thread的初始化方式我們應該並不陌生,下面介紹用ThreadGroup(創建的線程的同時,將其添加到某個線程組)來初始化一個Thread:

public Thread(ThreadGroup group, Runnable target) {
        init(group, target, "Thread-" + nextThreadNum(), 0);
    }
public Thread(ThreadGroup group, String name) {
        init(group, null, name, 0);
    }

 

線程的狀態及其切換過程                                                                                         


 

Thread源碼中其實只有上圖中的六種,因爲合併了Running和Runnable狀態,統稱爲Runnable。調用yield()函數的時候,其實就是由Running變爲Runnable狀態,而此時在Thread源碼中都是Runnable狀態。

 

sleep()                                                                                                                        


對外提供兩個重載版本:

//參數爲毫秒
public static void sleep(long millis) throws InterruptedException
//第一參數爲毫秒,第二個參數爲納秒
public static void sleep(long millis, int nanos) throws InterruptedException

sleep相當於讓線程睡眠,交出CPU,讓CPU去執行其他的任務。

  但是有一點要非常注意,sleep方法不會釋放鎖,也就是說如果當前線程持有對某個對象的鎖,則即使調用sleep方法,其他線程也無法訪問這個對象。看下面這個例子就清楚了:

public class Test {

    private int i = 10;
    private Object object = new Object();

    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread1 = test.new MyThread();
        MyThread thread2 = test.new MyThread();
        thread1.start();
        thread2.start();
    }


    class MyThread extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                i++;
                System.out.println("i:"+i);
                try {
                    System.out.println("線程"+Thread.currentThread().getName()+"進入睡眠狀態");
                    Thread.currentThread().sleep(10000);
                } catch (InterruptedException e) {
                    // TODO: handle exception
                }
                System.out.println("線程"+Thread.currentThread().getName()+"睡眠結束");
                i++;
                System.out.println("i:"+i);
            }
        }
    }
}
View Code

執行結果如下:

191016230185255

  從上面輸出結果可以看出,當Thread-0進入睡眠狀態之後,Thread-1並沒有去執行具體的任務。只有當Thread-0執行完之後,此時Thread-0釋放了對象鎖,Thread-1纔開始執行。

  注意,如果調用了sleep方法,必須捕獲InterruptedException異常或者將該異常向上層拋出。當線程睡眠時間滿後,不一定會立即得到執行,因爲此時可能CPU正在執行其他的任務。所以說調用sleep方法相當於讓線程進入阻塞狀態。

Java初高級一起學習分享,共同學習纔是最明智的選擇,喜歡的話可以我的學習羣64弍46衣3凌9,或加資料羣69似64陸0吧3

下面這段代碼是Thread源碼中的實現,前面部分是 參數異常檢查,如果參數有異常會拋出IllegalArgumentException異常。後面一部分主要是通過while循環控制sleep的結束(其實還是調用native的sleep方法)。

代碼裏面  獲取當前線程的lock,並在 sleep時不能讓其它線程訪問。(原因不是很清楚)

Object lock = currentThread().lock;

public static void sleep(long millis, int nanos)
    throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("millis < 0: " + millis);
        }
        if (nanos < 0) {
            throw new IllegalArgumentException("nanos < 0: " + nanos);
        }
        if (nanos > 999999) {
            throw new IllegalArgumentException("nanos > 999999: " + nanos);
        }

        // The JLS 3rd edition, section 17.9 says: "...sleep for zero
        // time...need not have observable effects."
        if (millis == 0 && nanos == 0) {
            // ...but we still have to handle being interrupted.
            if (Thread.interrupted()) {
              throw new InterruptedException();
            }
            return;
        }

        long start = System.nanoTime();
        long duration = (millis * NANOS_PER_MILLI) + nanos;  // NANOS_PER_MILLI 值爲 1000

        Object lock = currentThread().lock;

        // Wait may return early, so loop until sleep duration passes.
        synchronized (lock) {
            while (true) {
                sleep(lock, millis, nanos);  // 調用底層native sleep方法

                long now = System.nanoTime();
                long elapsed = now - start;

								// 當 sleep time 沒有達到指定的時間間隔時繼續調用 native 的sleep方法,
                if (elapsed >= duration) {
                    break;                   // sleep 中斷的條件
                }

                duration -= elapsed;
                start = now;
                millis = duration / NANOS_PER_MILLI;
                nanos = (int) (duration % NANOS_PER_MILLI);
            }
        }
    }
View Code

 

yield()                                                                                                                          


將Cpu讓給其它線程優先執行,自己進入等待執行(Runnable)狀態。yield函數沒有設置等待執行的時間,一切聽從cpu的調度,當沒有其它線程搶佔cpu時,當前線程又會被cpu調度進入Running狀態。它跟sleep方法類似,同樣不會釋放鎖

當增加yield()執行下面這段代碼和沒有yield()時的區別在於:當增加yield()函數後循環執行到149時將cpu的使用權讓給了另一個線程執行,知道另一個線程執行完畢再從149自增打印輸出。

複製代碼
public class SelfTest {

    public static class ReadThread extends Thread {
        int i = 0;
        public void run() {
            while(i<300 ){
                System. out.println("*******  "+Thread.currentThread().getId()+“    **********: "+i++);
                if(150==i){
                    Thread. yield();
                }
            }
            System. out.println(number+"   currentThread: "+Thread.currentThread());
        }
    }
    public static void main(String [] args) {
        new ReadThread().start();
        new ReadThread().start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

Thread源碼中並沒有看到六種狀態切換的代碼,相必都在c/c++層去做了。

currentThread()

public static native Thread currentThread();

該方法是用來獲取當前線程,實現都是在native層做的,無法看到代碼。

interrupt()                                                                                                                     


interrupt,顧名思義,即中斷的意思。單獨調用interrupt方法可以使得處於阻塞狀態的線程拋出一個異常,也就說,它可以用來中斷一個正處於阻塞狀態的線程;另外,通過interrupt方法和isInterrupted()方法來停止正在運行的線程。

下面看一個例子:

public class Test {

    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {

        }
        thread.interrupt();
    }

    class MyThread extends Thread{
        @Override
        public void run() {
            try {
                System.out.println("進入睡眠狀態");
                Thread.currentThread().sleep(10000);
                System.out.println("睡眠完畢");
            } catch (InterruptedException e) {
                System.out.println("得到中斷異常");
            }
            System.out.println("run方法執行完畢");
        }
    }
}
View Code

輸出結果:

從這裏可以看出,通過interrupt方法可以中斷處於阻塞狀態的線程。那麼能不能中斷處於非阻塞狀態的線程呢?看下面這個例子:

複製代碼
public class Test {

    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {

        }
        thread.interrupt();
    }

    class MyThread extends Thread{
        @Override
        public void run() {
            int i = 0;
            while(i<Integer.MAX_VALUE){
                System.out.println(i+" while循環");
                i++;
            }
        }
    }
}
複製代碼

運行該程序會發現,while循環會一直運行直到變量i的值超出Integer.MAX_VALUE。所以說直接調用interrupt方法不能中斷正在運行中的線程。

  但是如果配合isInterrupted()能夠中斷正在運行的線程,因爲調用interrupt方法相當於將中斷標誌位置爲true,那麼可以通過調用isInterrupted()判斷中斷標誌是否被置位來中斷線程的執行。比如下面這段代碼:

複製代碼
public class Test {

    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {

        }
        thread.interrupt();
    }

    class MyThread extends Thread{
        @Override
        public void run() {
            int i = 0;
            while(!isInterrupted() && i<Integer.MAX_VALUE){
                System.out.println(i+" while循環");
                i++;
            }
        }
    }
}
複製代碼

運行會發現,打印若干個值之後,while循環就停止打印了。

  但是一般情況下不建議通過這種方式來中斷線程,一般會在MyThread類中增加一個屬性 isStop來標誌是否結束while循環,然後再在while循環中判斷isStop的值。

複製代碼
class MyThread extends Thread{
        private volatile boolean isStop = false;
        @Override
        public void run() {
            int i = 0;
            while(!isStop){
                i++;
            }
        }

        public void setStop(boolean stop){
            this.isStop = stop;
        }
    }
複製代碼

那麼就可以在外面通過調用setStop方法來終止while循環。

wait()/notify()                                                                                                                


 

複製代碼
public class WaitNotifyCase {
    public static void main(String[] args) {
        final Object lock = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (lock) {
                    try {
                        System.out.println("thread A get lock");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println("thread A do wait method");
                        lock.wait();
                        System.out.println("wait end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread B is waiting to get lock");
                synchronized (lock) {
                    System.out.println("thread B get lock");
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread B do notify method");
                }
            }
        }).start();
    }
}
複製代碼

執行結果:

複製代碼
thread A get lock
thread A do wait method
thread B is waiting to get lock
thread B get lock
thread B do notify method
wait end
複製代碼

多線程共享資源時,通過共享資源的 wait()和notify()進行通信,上例子中

1、 A線程首先持有了資源lock,並進入了Synchronized同步塊

2、A 調用 共享資源的wait()方法,當前線程狀態進入TIMED_WAITING狀態,同時釋放資源鎖。wait()後面的代碼只有當該線程被喚醒的時候纔回去執行(notify())

3、B線程獲取到資源鎖,進入同步塊,並調用了資源額notify()方法,喚醒了A線程(注意:此時線程B並沒有停止執行,而去執行A線程,而是等B線程執行完之後A線程才能被真正喚醒)

疑問: 問什麼調用wait() 和 notify()方法之前必須 將資源放在Synchronized塊中?

參看 Object源碼註釋或者其它資料就會發現,這兩個方法執行之前必須獲取到資源(實例中的lock對象)的monitor。將資源方到同步塊中可以保證當前線程獲取到資源的monitor

 

當調用資源的wait方法時,將該線程放到資源的等待集合中,並放棄資源的同步請求(放棄同步鎖)

This method causes the current thread (call it <var>T</var>) to
* place itself in the wait set for this object and then to relinquish
* any and all synchronization claims on this object.

 

notify()方法調用時,會去 wait set中任意選擇一個線程進行喚醒

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation

當調用notifyAll()方法時則會喚醒wait set中的所有線程

 

join()                                                                                                                                 


很多情況下 當 A線程需要等待B線程執行完之後才能去執行,對於這種場景我們就可以用到 join()函數了。

複製代碼
class BThread extends Thread {
    public BThread() {
        super("[BThread] Thread");
    };
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(threadName + " loop at " + i);
                Thread.sleep(1000);
            }
            System.out.println(threadName + " end.");
        } catch (Exception e) {
            System.out.println("Exception from " + threadName + ".run");
        }
    }
}
class AThread extends Thread {
    BThread bt;
    public AThread(BThread bt) {
        super("[AThread] Thread");
        this.bt = bt;
    }
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        try {
            bt.join();
            System.out.println(threadName + " end.");
        } catch (Exception e) {
            System.out.println("Exception from " + threadName + ".run");
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        BThread bt = new BThread();
        AThread at = new AThread(bt);
        try {
            bt.start();
            Thread.sleep(2000);
            at.start();
            at.join();
        } catch (Exception e) {
            System.out.println("Exception from main");
        }
        System.out.println(threadName + " end!");
    }
}
複製代碼

執行結果如下:

複製代碼
main start.    //主線程起動,因爲調用了at.join(),要等到at結束了,此線程才能向下執行。 
[BThread] Thread start.
[BThread] Thread loop at 0
[BThread] Thread loop at 1
[AThread] Thread start.    //線程at啓動,因爲調用bt.join(),等到bt結束了才向下執行。 
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.
[AThread] Thread end.    // 線程AThread在bt.join();阻塞處起動,向下繼續執行的結果 
main end!      //線程AThread結束,此線程在at.join();阻塞處起動,向下繼續執行的結果。
複製代碼

線程B先執行,當A執行後,調用B線程的join()方法,表示等待B的run()方法執行結束之後纔會繼續往下執行。

 

suspend()/stop()/resume()是已經過時的方法,這裏就不再介紹,可能是因爲他們容易造成死鎖,所以不建議使用

發佈了4 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章