02.併發編程(2)Thread類源碼分析

概述

在說線程以前先說下進程,進程和線程都是一個時間段的描述,是CPU工做時間段的描述。java

進程,是併發執行的程序在執行過程當中分配和管理資源的基本單位,是一個動態概念,竟爭計算機系統資源的基本單位。每個進程都有一個本身的地址空間,即進程空間或(虛空間)。git

線程,在網絡或多用戶環境下,一個服務器一般須要接收大量且不肯定數量用戶的併發請求,爲每個請求都建立一個進程顯然是行不通的,——不管是從系統資源開銷方面或是響應用戶請求的效率方面來看。所以,操做系統中線程的概念便被引進了。線程,是進程的一部分,一個沒有線程的進程能夠被看做是單線程的。線程有時又被稱爲輕權進程或輕量級進程,也是 CPU 調度的一個基本單位。程序員

建立方式

線程的建立有三種方式:繼承Thread,實現Runnable接口,利用Callable跟Futuregithub

繼承Thread

(1)定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就表明了線程要完成的任務。所以把run()方法稱爲執行體。 (2)建立Thread子類的實例,即建立了線程對象。 (3)調用線程對象的start()方法來啓動該線程。編程

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

實現Runnable接口

  • (1)定義runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體一樣是該線程的線程執行體。
  • (2)建立 Runnable實現類的實例,並依此實例做爲Thread的target來建立Thread對象,該Thread對象纔是真正的線程對象。
  • (3)調用線程對象的start()方法來啓動該線程。
public class SecondMethod implements Runnable{
        @Override
        public void run() {

        }
    }
SecondMethod secondMethod=new SecondMethod();
new Thread(secondMethod).start();
複製代碼

經過Callable跟FutureTask建立線程

1)建立Callable接口的實現類,並實現call()方法,該call()方法將做爲線程執行體,而且有返回值。 (2)建立Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。 (3)使用FutureTask對象做爲Thread對象的target建立並啓動新線程。 (4)調用FutureTask對象的get()方法來得到子線程執行結束後的返回值數組

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();
        } 
複製代碼

對比分析

實現Runnable和實現Callable接口的方式基本相同,不過是後者執行call()方法有返回值,後者線程執行體run()方法無返回值,所以能夠把這兩種方式歸爲一種這種方式與繼承Thread類的方法之間的差異以下:bash

一、接口建立線程能夠實現資源共享,好比多個線程能夠共享一個Runnable資源 二、可是編程稍微複雜,若是須要訪問當前線程,必須調用Thread.currentThread()方法。 三、接口建立線能夠避免因爲Java的單繼承特性而帶來的侷限。服務器

如今經過一個程序員改Bug的例子來描述一下,一共有15個bug,如今安排3個程序員去Debug:網絡

經過Thread來實現併發

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();

    }
}
複製代碼

運行結果:

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
複製代碼

經過Runnable來實現

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();

    }
複製代碼

運行結果

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[];//線程名稱的字節數組
	private int    priority;//線程優先級
    private boolean single_step;  //線程是否單步
    private boolean daemon = false;  //是不是守護線程
    private boolean stillborn = false; //JVM state
    private Runnable target;  //從構造方法傳過來的Runnable
    private ThreadGroup group; //線程組
    private ClassLoader contextClassLoader;  //類加載器
    private static int threadInitNumber;  //線程編號
    private volatile int threadStatus = 0; //初始狀態
    public final static int MIN_PRIORITY = 1; //最低優先級
    public final static int NORM_PRIORITY = 5; //默認優先級
    public final static int MAX_PRIORITY = 10; //最高優先級
  
複製代碼

線程狀態

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;
    }
複製代碼

線程的狀態有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,能夠整理成以下表格

線程狀態 解釋
New 還未調用 start() 方法
RUNNABLE 調用了 start() ,此時線程已經準備好被執行,處於就緒隊列
BLOCKED 線程阻塞於鎖或者調用了 sleep
WAITING 線程因爲某種緣由等待其餘線程
TIME_WAITING 與 WAITING 的區別是能夠在特定時間後自動返回
TERMINATED 執行完畢或者被其餘線程殺死

構造方法

thread_constructor
Thread有不少構造方法,可是經過觀察最終調用了以下方法:

/** * Initializes a Thread. * * @param g //線程組 * @param target //構造方法傳過來的Runnable * @param name //線程名稱 * @param stackSize //給線程分配的棧的深度 * @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();
        //判斷線程組參數是否爲空
        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;//初始化線程組
        this.daemon = parent.isDaemon();//定義是否爲守護線程
        this.priority = parent.getPriority();//設置優先級
        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);//設置優先級
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;//設置棧深度
        /* Set thread ID */
        tid = nextThreadID();//設置線程ID
    }
複製代碼

start方法

public synchronized void start() {
   
        if (threadStatus != 0)//判斷線程是否準備好
        group.add(this);//將啓動的線程線程組
        boolean started = false;
        try {
            start0();//本地方法,JVM調用target的run方法
            started = true;//更改啓動標誌
        } finally {
            try {
                if (!started)
                    group.threadStartFailed(this);//通知線程組啓動失敗
                }
            } 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 關鍵字說明start方法是同步的,而且是啓動這個線程進行執行,JVM將會調用這個線程的run方法,這樣產生的結果是,兩個線程執行着,其中一個是調用start()方法的線程執行,另外一個線程是執行run方法的線程。

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);//調用本地方法
    }
複製代碼

線程休眠一段時間,讓其餘線程有機會繼續執行,須要捕捉異常。

yield()方法

public static native void yield();
複製代碼
  • yield是一個靜態的原生(native)方法
  • yield告訴當前正在執行的線程把運行機會交給線程池中擁有相同優先級的線程。
  • yield不能保證使得當前正在運行的線程迅速轉換到可運行的狀態 它僅能使一個線程從運行狀態轉到可運行狀態,而不是等待或阻塞狀態

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方法是等待該線程執行,直到超時或者終止,能夠做爲線程通訊的一種方式,A線程調用B線程的join(阻塞),等待B完成後再往下執行。

yieldjoin

  • join方法用線程對象調用,若是在一個線程A中調用另外一個線程B的join方法,線程A將會等待線程B執行完畢後再執行。
  • yield能夠直接用Thread類調用,yield讓出CPU執行權給同等級的線程,若是沒有相同級別的線程在等待CPU的執行權,則該線程繼續執行。

interrupt()方法

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

interrupt()方法是中斷當前的線程, 此外還有isInterrupt,以及interrupted方法

  • interrupt():將線程置爲中斷狀態
  • isInterrupt():線程是否中斷
  • interrupted():返回線程的上次的中斷狀態,並清除中斷狀態。 通常來講,阻塞函數:如sleep()、join()、wait()等在檢查到線程的中斷狀態的時候,會拋出InteruptedExeption, 同時會清除線程的中斷狀態。

線程間通訊

前面說過,Java中的線程在底層是經過共享內存進行通訊的,在應用層則是經過調用Object對象的wait()方法和notify()方法或notifyAll()方法來實現線程間的通訊。 Object是全部類的超類,主要經過:notify()、notifyAll()、wait()、wait(long)和wait(long,int)這幾個方法來進行線程間通訊。

一、wait()

public final void wait()  throws InterruptedException,IllegalMonitorStateException
複製代碼
  • 休眠當前線程,釋放鎖,直到接到通知或被中斷爲止
  • 在調用wait()以前,線程必需要得到該對象的對象級別鎖

二、notify()

public final native void notify() throws IllegalMonitorStateException
複製代碼
  • 通知那些調用了wait()方法的線程。
  • 每次只能通知單個線程,單個線程等待,則通知當前線程,若是有多個,則隨機抽取一個來進行通知
  • 必須等到當前線程釋放鎖後,wait所在的線程也才能夠獲取該對象鎖,但不驚動其餘一樣在等待被該對象notify的線程們。
  • wait()等待的是被notify或notifyAll,而不是鎖。

三、notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException
複製代碼
  • 使全部原來在該對象上wait的線程通通退出wait的狀態
  • 全部被通知的線程都會去競爭對象鎖。
  • 得到鎖的線程,會繼續往下執行,釋放鎖後,wait中的線程繼續競爭對象鎖

wait()和sleep()的區別

  • sleep()方法是線程類Thread的靜態方法,致使此線程暫停執行指定時間,將執行機會給其餘線程,可是監控狀態依然保持,到時後會自動恢復(線程回到就緒(ready)狀態),由於調用sleep 不會釋放對象鎖。
  • wait()是Object 類的方法,對此對象調用wait()方法致使本線程放棄對象鎖(線程暫停執行),進入等待此對象的等待鎖定池,只有針對此對象發出notify 方法(或notifyAll)後本線程才進入對象鎖定池準備得到對象鎖進入就緒狀態。

總結

經過對線程源碼的簡單分析,能夠看出線程也是有本身的生命週期的,可是因爲源碼中有不少native方法,致使了很難追蹤源碼,因此只能大體理解一下線程的各類狀態以及通訊過程,下面能夠經過一副流程圖來總結一下:

Thread_life

參考資料

Java編程思想

wangchangchung.github.io

www.jianshu.com/p/5b9fdae43…

相關文章
相關標籤/搜索