02.并發(fā)編程(2)Thread類(lèi)源碼分析

概述

在說(shuō)線(xiàn)程之前先說(shuō)下進(jìn)程,進(jìn)程和線(xiàn)程都是一個(gè)時(shí)間段的描述,是CPU工作時(shí)間段的描述。

進(jìn)程,是并發(fā)執(zhí)行的程序在執(zhí)行過(guò)程中分配和管理資源的基本單位,是一個(gè)動(dòng)態(tài)概念,竟?fàn)幱?jì)算機(jī)系統(tǒng)資源的基本單位。每一個(gè)進(jìn)程都有一個(gè)自己的地址空間,即進(jìn)程空間或(虛空間)。

線(xiàn)程,在網(wǎng)絡(luò)或多用戶(hù)環(huán)境下,一個(gè)服務(wù)器通常需要接收大量且不確定數(shù)量用戶(hù)的并發(fā)請(qǐng)求,為每一個(gè)請(qǐng)求都創(chuàng)建一個(gè)進(jìn)程顯然是行不通的,——無(wú)論是從系統(tǒng)資源開(kāi)銷(xiāo)方面或是響應(yīng)用戶(hù)請(qǐng)求的效率方面來(lái)看。因此,操作系統(tǒng)中線(xiàn)程的概念便被引進(jìn)了。線(xiàn)程,是進(jìn)程的一部分,一個(gè)沒(méi)有線(xiàn)程的進(jìn)程可以被看作是單線(xiàn)程的。線(xiàn)程有時(shí)又被稱(chēng)為輕權(quán)進(jìn)程或輕量級(jí)進(jìn)程,也是 CPU 調(diào)度的一個(gè)基本單位。

創(chuàng)建方式

線(xiàn)程的創(chuàng)建有三種方式:繼承Thread,實(shí)現(xiàn)Runnable接口,利用Callable跟Future

繼承Thread

(1)定義Thread類(lèi)的子類(lèi),并重寫(xiě)該類(lèi)的run方法,該run方法的方法體就代表了線(xiàn)程要完成的任務(wù)。因此把run()方法稱(chēng)為執(zhí)行體。
(2)創(chuàng)建Thread子類(lèi)的實(shí)例,即創(chuàng)建了線(xiàn)程對(duì)象。
(3)調(diào)用線(xiàn)程對(duì)象的start()方法來(lái)啟動(dòng)該線(xiàn)程。

    public class FirstMethod extends Thread {
        @Override
        public void run() {
            super.run();
        }
    }
      FirstMethod firstMethod = new FirstMethod();
      firstMethod.start();

實(shí)現(xiàn)Runnable接口

  • (1)定義runnable接口的實(shí)現(xiàn)類(lèi),并重寫(xiě)該接口的run()方法,該run()方法的方法體同樣是該線(xiàn)程的線(xiàn)程執(zhí)行體。
  • (2)創(chuàng)建 Runnable實(shí)現(xiàn)類(lèi)的實(shí)例,并依此實(shí)例作為T(mén)hread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線(xiàn)程對(duì)象。
  • (3)調(diào)用線(xiàn)程對(duì)象的start()方法來(lái)啟動(dòng)該線(xiàn)程。
public class SecondMethod implements Runnable{
        @Override
        public void run() {

        }
    }
SecondMethod secondMethod=new SecondMethod();
new Thread(secondMethod).start();

通過(guò)Callable跟FutureTask創(chuàng)建線(xiàn)程

1)創(chuàng)建Callable接口的實(shí)現(xiàn)類(lèi),并實(shí)現(xiàn)call()方法,該call()方法將作為線(xiàn)程執(zhí)行體,并且有返回值。
(2)創(chuàng)建Callable實(shí)現(xiàn)類(lèi)的實(shí)例,使用FutureTask類(lèi)來(lái)包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該Callable對(duì)象的call()方法的返回值。
(3)使用FutureTask對(duì)象作為T(mén)hread對(duì)象的target創(chuàng)建并啟動(dòng)新線(xiàn)程。
(4)調(diào)用FutureTask對(duì)象的get()方法來(lái)獲得子線(xiàn)程執(zhí)行結(jié)束后的返回值

  public class ThirdMethod implements Callable<String> {
        @Override
        public String call() throws Exception {
            return Thread.currentThread().getName();
        }
    }
   
   ThirdMethod thirdMethod=new ThirdMethod();
   FutureTask<String> futureTask=new FutureTask<String>(thirdMethod);
        try {
            String threadName = futureTask.get();

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

對(duì)比分析

實(shí)現(xiàn)Runnable和實(shí)現(xiàn)Callable接口的方式基本相同,不過(guò)是后者執(zhí)行call()方法有返回值,后者線(xiàn)程執(zhí)行體run()方法無(wú)返回值,因此可以把這兩種方式歸為一種這種方式與繼承Thread類(lèi)的方法之間的差別如下:

1、接口創(chuàng)建線(xiàn)程可以實(shí)現(xiàn)資源共享,比如多個(gè)線(xiàn)程可以共享一個(gè)Runnable資源
2、但是編程稍微復(fù)雜,如果需要訪(fǎng)問(wèn)當(dāng)前線(xiàn)程,必須調(diào)用Thread.currentThread()方法。
3、接口創(chuàng)建線(xiàn)可以避免由于Java的單繼承特性而帶來(lái)的局限。

現(xiàn)在通過(guò)一個(gè)程序員改Bug的例子來(lái)描述一下,一共有15個(gè)bug,現(xiàn)在安排3個(gè)程序員去Debug:

通過(guò)Thread來(lái)實(shí)現(xiàn)

public class BugThread extends Thread {
    private volatile int bugNumber = 5;

    @Override
    public void run() {
        for (int i = 0; i < bugNumber; i++) {
            System.out.println("bugNumber--->" + bugNumber--);
        }
    }
}
  
public class Main {
    public static void main(String[] args) {
        new BugThread().start();
        new BugThread().start();
        new BugThread().start();

    }
}

運(yùn)行結(jié)果:

Thread-0-----5
Thread-1-----5
Thread-2-----5
Thread-0-----4
Thread-2-----4
Thread-1-----4
Thread-2-----3
Thread-0-----3
Thread-2-----2
Thread-1-----3
Thread-2-----1
Thread-0-----2
Thread-0-----1
Thread-1-----2
Thread-1-----1

通過(guò)Runnable來(lái)實(shí)現(xiàn)

public class BugRunnable implements Runnable {
    private volatile int bugNumber = 15;

    @Override
    public void run() {
        while (bugNumber > 0)
 System.out.println(Thread.currentThread().getName() + "-----" + bugNumber--);
    }
}
  
    public static void main(String[] args) {
        BugRunnable bugRunnable = new BugRunnable();
        new Thread(bugRunnable).start();
        new Thread(bugRunnable).start();
        new Thread(bugRunnable).start();

    }

運(yùn)行結(jié)果

Thread-0-----15
Thread-0-----14
Thread-0-----13
Thread-0-----12
Thread-1-----11
Thread-0-----10
Thread-1-----9
Thread-0-----8
Thread-1-----7
Thread-0-----6
Thread-1-----5
Thread-0-----4
Thread-1-----3
Thread-0-----2
Thread-1-----1

源碼分析

成員變量

    private volatile char  name[];//線(xiàn)程名稱(chēng)的字節(jié)數(shù)組
    private int    priority;//線(xiàn)程優(yōu)先級(jí)
    private boolean single_step;  //線(xiàn)程是否單步
    private boolean daemon = false;  //是否是守護(hù)線(xiàn)程
    private boolean stillborn = false; //JVM state
    private Runnable target;  //從構(gòu)造方法傳過(guò)來(lái)的Runnable
    private ThreadGroup group; //線(xiàn)程組
    private ClassLoader contextClassLoader;  //類(lèi)加載器
    private static int threadInitNumber;  //線(xiàn)程編號(hào)
    private volatile int threadStatus = 0; //初始狀態(tài)
    public final static int MIN_PRIORITY = 1; //最低優(yōu)先級(jí)
    public final static int NORM_PRIORITY = 5; //默認(rèn)優(yōu)先級(jí)
    public final static int MAX_PRIORITY = 10; //最高優(yōu)先級(jí)
  

線(xiàn)程狀態(tài)

 public enum State {
       //Thread state for a thread which has not yet started.
        NEW,
       //Thread state for a runnable thread. 
        RUNNABLE,
       //Thread state for a thread blocked waiting for a monitor lock.
        BLOCKED,
      // Thread state for a waiting thread.
        WAITING,
      //Thread state for a waiting thread with a specified waiting time.
        TIMED_WAITING,
      //Thread state for a terminated thread
        TERMINATED;
    }
線(xiàn)程狀態(tài) 解釋
New 還未調(diào)用 start() 方法
RUNNABLE 調(diào)用了 start() ,此時(shí)線(xiàn)程已經(jīng)準(zhǔn)備好被執(zhí)行,處于就緒隊(duì)列
BLOCKED 線(xiàn)程阻塞于鎖或者調(diào)用了 sleep
WAITING 線(xiàn)程由于某種原因等待其他線(xiàn)程
TIME_WAITING 與 WAITING 的區(qū)別是可以在特定時(shí)間后自動(dòng)返回
TERMINATED 執(zhí)行完畢或者被其他線(xiàn)程殺死

線(xiàn)程的狀態(tài)有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格

線(xiàn)程狀態(tài) 解釋
New 還未調(diào)用 start() 方法
RUNNABLE 調(diào)用了 start() ,此時(shí)線(xiàn)程已經(jīng)準(zhǔn)備好被執(zhí)行,處于就緒隊(duì)列
BLOCKED 線(xiàn)程阻塞于鎖或者調(diào)用了 sleep
WAITING 線(xiàn)程由于某種原因等待其他線(xiàn)程
TIME_WAITING 與 WAITING 的區(qū)別是可以在特定時(shí)間后自動(dòng)返回
TERMINATED 執(zhí)行完畢或者被其他線(xiàn)程殺死

構(gòu)造方法

thread_constructor

Thread有很多構(gòu)造方法,但是通過(guò)觀(guān)察最終調(diào)用了如下方法:

  /**
     * Initializes a Thread.
     *
     * @param g //線(xiàn)程組
     * @param target //構(gòu)造方法傳過(guò)來(lái)的Runnable
     * @param name //線(xiàn)程名稱(chēng)
     * @param stackSize //給線(xiàn)程分配的棧的深度
     * @param acc //上下文加載器
     */
   private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name.toCharArray();
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        //判斷線(xiàn)程組參數(shù)是否為空
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();

        if (security != null) {
            if (isCCLOverridden(getClass())) {
        security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
        this.group = g;//初始化線(xiàn)程組
        this.daemon = parent.isDaemon();//定義是否為守護(hù)線(xiàn)程
        this.priority = parent.getPriority();//設(shè)置優(yōu)先級(jí)
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;//初始化target
        setPriority(priority);//設(shè)置優(yōu)先級(jí)
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;//設(shè)置棧深度
        /* Set thread ID */
        tid = nextThreadID();//設(shè)置線(xiàn)程ID
    }

start方法

    public synchronized void start() {
   
        if (threadStatus != 0)//判斷線(xiàn)程是否準(zhǔn)備好
        group.add(this);//將啟動(dòng)的線(xiàn)程線(xiàn)程組
        boolean started = false;
        try {
            start0();//本地方法,JVM調(diào)用target的run方法
            started = true;//更改啟動(dòng)標(biāo)志
        } finally {
            try {
                if (!started)
                    group.threadStartFailed(this);//通知線(xiàn)程組啟動(dòng)失敗
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

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

synchronized 關(guān)鍵字說(shuō)明start方法是同步的,并且是啟動(dòng)這個(gè)線(xiàn)程進(jìn)行執(zhí)行,JVM將會(huì)調(diào)用這個(gè)線(xiàn)程的run方法,這樣產(chǎn)生的結(jié)果是,兩個(gè)線(xiàn)程執(zhí)行著,其中一個(gè)是調(diào)用start()方法的線(xiàn)程執(zhí)行,另一個(gè)線(xiàn)程是執(zhí)行run方法的線(xiàn)程。

sleep()方法

 public static void sleep(long millis, int nanos)
    throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);//調(diào)用本地方法
    }

線(xiàn)程休眠一段時(shí)間,讓其他線(xiàn)程有機(jī)會(huì)繼續(xù)執(zhí)行,需要捕捉異常。

yield()方法

  public static native void yield();
  • yield是一個(gè)靜態(tài)的原生(native)方法
  • yield告訴當(dāng)前正在執(zhí)行的線(xiàn)程把運(yùn)行機(jī)會(huì)交給線(xiàn)程池中擁有相同優(yōu)先級(jí)的線(xiàn)程。
  • yield不能保證使得當(dāng)前正在運(yùn)行的線(xiàn)程迅速轉(zhuǎn)換到可運(yùn)行的狀態(tài)
    它僅能使一個(gè)線(xiàn)程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),而不是等待或阻塞狀態(tài)

join()方法

  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()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

join方法是等待該線(xiàn)程執(zhí)行,直到超時(shí)或者終止,可以作為線(xiàn)程通信的一種方式,A線(xiàn)程調(diào)用B線(xiàn)程的join(阻塞),等待B完成后再往下執(zhí)行。

yieldjoin

  • join方法用線(xiàn)程對(duì)象調(diào)用,如果在一個(gè)線(xiàn)程A中調(diào)用另一個(gè)線(xiàn)程B的join方法,線(xiàn)程A將會(huì)等待線(xiàn)程B執(zhí)行完畢后再執(zhí)行。
  • yield可以直接用Thread類(lèi)調(diào)用,yield讓出CPU執(zhí)行權(quán)給同等級(jí)的線(xiàn)程,如果沒(méi)有相同級(jí)別的線(xiàn)程在等待CPU的執(zhí)行權(quán),則該線(xiàn)程繼續(xù)執(zhí)行。

interrupt()方法

    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();//檢查權(quán)限
synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();        
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

interrupt()方法是中斷當(dāng)前的線(xiàn)程, 此外還有isInterrupt,以及interrupted方法

  • interrupt():將線(xiàn)程置為中斷狀態(tài)
  • isInterrupt():線(xiàn)程是否中斷
  • interrupted():返回線(xiàn)程的上次的中斷狀態(tài),并清除中斷狀態(tài)。
    一般來(lái)說(shuō),阻塞函數(shù):如sleep()、join()、wait()等在檢查到線(xiàn)程的中斷狀態(tài)的時(shí)候,會(huì)拋出InteruptedExeption, 同時(shí)會(huì)清除線(xiàn)程的中斷狀態(tài)。

線(xiàn)程間通信

前面說(shuō)過(guò),Java中的線(xiàn)程在底層是通過(guò)共享內(nèi)存進(jìn)行通信的,在應(yīng)用層則是通過(guò)調(diào)用Object對(duì)象的wait()方法和notify()方法或notifyAll()方法來(lái)實(shí)現(xiàn)線(xiàn)程間的通信。
Object是所有類(lèi)的超類(lèi),主要通過(guò):notify()、notifyAll()、wait()、wait(long)和wait(long,int)這幾個(gè)方法來(lái)進(jìn)行線(xiàn)程間通信。

1、wait()

public final void wait()  throws InterruptedException,IllegalMonitorStateException
  • 休眠當(dāng)前線(xiàn)程,釋放鎖,直到接到通知或被中斷為止
  • 在調(diào)用wait()之前,線(xiàn)程必須要獲得該對(duì)象的對(duì)象級(jí)別鎖

2、notify()

public final native void notify() throws IllegalMonitorStateException
  • 通知那些調(diào)用了wait()方法的線(xiàn)程。
  • 每次只能通知單個(gè)線(xiàn)程,單個(gè)線(xiàn)程等待,則通知當(dāng)前線(xiàn)程,如果有多個(gè),則隨機(jī)抽取一個(gè)來(lái)進(jìn)行通知
  • 必須等到當(dāng)前線(xiàn)程釋放鎖后,wait所在的線(xiàn)程也才可以獲取該對(duì)象鎖,但不驚動(dòng)其他同樣在等待被該對(duì)象notify的線(xiàn)程們。
  • wait()等待的是被notify或notifyAll,而不是鎖。

3、notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException
  • 使所有原來(lái)在該對(duì)象上wait的線(xiàn)程統(tǒng)統(tǒng)退出wait的狀態(tài)
  • 所有被通知的線(xiàn)程都會(huì)去競(jìng)爭(zhēng)對(duì)象鎖。
  • 獲得鎖的線(xiàn)程,會(huì)繼續(xù)往下執(zhí)行,釋放鎖后,wait中的線(xiàn)程繼續(xù)競(jìng)爭(zhēng)對(duì)象鎖

wait()和sleep()的區(qū)別

  • sleep()方法是線(xiàn)程類(lèi)Thread的靜態(tài)方法,導(dǎo)致此線(xiàn)程暫停執(zhí)行指定時(shí)間,將執(zhí)行機(jī)會(huì)給其他線(xiàn)程,但是監(jiān)控狀態(tài)依然保持,到時(shí)后會(huì)自動(dòng)恢復(fù)(線(xiàn)程回到就緒(ready)狀態(tài)),因?yàn)檎{(diào)用sleep 不會(huì)釋放對(duì)象鎖。
  • wait()是Object 類(lèi)的方法,對(duì)此對(duì)象調(diào)用wait()方法導(dǎo)致本線(xiàn)程放棄對(duì)象鎖(線(xiàn)程暫停執(zhí)行),進(jìn)入等待此對(duì)象的等待鎖定池,只有針對(duì)此對(duì)象發(fā)出notify 方法(或notifyAll)后本線(xiàn)程才進(jìn)入對(duì)象鎖定池準(zhǔn)備獲得對(duì)象鎖進(jìn)入就緒狀態(tài)。

總結(jié)

通過(guò)對(duì)線(xiàn)程源碼的簡(jiǎn)單分析,可以看出線(xiàn)程也是有自己的生命周期的,但是由于源碼中有很多native方法,導(dǎo)致了很難追蹤源碼,所以只能大致理解一下線(xiàn)程的各種狀態(tài)以及通信過(guò)程,下面可以通過(guò)一副流程圖來(lái)總結(jié)一下:


Thread_life

參考資料

Java編程思想
https://wangchangchung.github.io
http://www.lxweimin.com/p/5b9fdae43335

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 下面是我自己收集整理的Java線(xiàn)程相關(guān)的面試題,可以用它來(lái)好好準(zhǔn)備面試。 參考文檔:-《Java核心技術(shù) 卷一》-...
    阿呆變Geek閱讀 14,887評(píng)論 14 507
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來(lái)依藍(lán)閱讀 7,378評(píng)論 3 87
  • 寫(xiě)在前面的話(huà): 這篇博客是我從這里“轉(zhuǎn)載”的,為什么轉(zhuǎn)載兩個(gè)字加“”呢?因?yàn)檫@絕不是簡(jiǎn)單的復(fù)制粘貼,我花了五六個(gè)小...
    SmartSean閱讀 4,776評(píng)論 12 45
  • 原文鏈接 譯者:靖靖 并發(fā) 進(jìn)程和線(xiàn)程 在并發(fā)編程當(dāng)中,有兩個(gè)基本的執(zhí)行單元:進(jìn)程和線(xiàn)程。在java中,我們大部分...
    4b4f3ceb6f71閱讀 816評(píng)論 4 16
  • “油紙傘”說(shuō): 歲月在,我在,你也在,時(shí)光剛剛好,相遇,滿(mǎn)眸清歡,別離,不說(shuō)再見(jiàn),流年,渡口轉(zhuǎn)彎,一眼瞬間,你我再...
    杉茶花閱讀 376評(píng)論 0 2