【高并發(fā)】1、線程的基本使用

一、線程中的概念

  • 進(jìn)程:程序運(yùn)行資源分配的最小單位,進(jìn)程內(nèi)部有多個(gè)線程,會(huì)共享這個(gè)進(jìn)程的資源;
  • 線程:CPU 調(diào)度的最小單位,在一個(gè) Java 程序進(jìn)程中,最少有 2 個(gè)線程,Main 線程 與 GC 守護(hù)線程;
  • 上下文切換:CPU 為每個(gè)線程分配一個(gè)時(shí)間段,它稱為時(shí)間片,如果在時(shí)間片結(jié)束時(shí)線程還在運(yùn)行,則暫停這個(gè)線程的運(yùn)行,并且 CPU 分配給另一個(gè)線程;
  • 并行: 同一時(shí)刻,可以處理事情的能力,真正意義上的多個(gè)任務(wù)同時(shí)執(zhí)行;
  • 并發(fā):在單位時(shí)間內(nèi),可以處理事情的能力,多個(gè)任務(wù)交替執(zhí)行;
  • 線程模型:
    • 用戶級(jí)線程(ULT):用戶程序?qū)崿F(xiàn),不依賴操作系統(tǒng)核心,應(yīng)用提供創(chuàng)建、同步、調(diào)度和管理線程的函數(shù)來控制用戶線程。不需要用戶態(tài)/核心態(tài)切換,速度快。內(nèi)核對(duì) ULT 無感知,線程阻塞則進(jìn)程阻塞;
    • 內(nèi)核級(jí)線程(KLT):系統(tǒng)內(nèi)核管理線程,內(nèi)核保存線程的狀態(tài)和上下文信息,線程阻塞不會(huì)引起進(jìn)程阻塞。在多處理器系統(tǒng)上,多線程在多處理器上并行運(yùn)行。線程的創(chuàng)建、調(diào)度和管理由內(nèi)核完成,效率比 ULT 要慢,進(jìn)程操作要快;

Java 線程創(chuàng)建是依賴于系統(tǒng)內(nèi)核(KLT),通過 JVM 調(diào)用系統(tǒng)庫創(chuàng)建內(nèi)核線程,內(nèi)核線程與 Java 線程是 1:1 的映射關(guān)系。

二、Thread 類解析

2.1 線程的狀態(tài)

線程的狀態(tài)

線程狀態(tài)定義位于 Thread.State 枚舉類中,定義了如下 6 種狀態(tài)

  • NEW(新建狀態(tài)):實(shí)現(xiàn) Runnable 或繼承 Thread 使用 new 關(guān)鍵字實(shí)例對(duì)象,但沒有調(diào)用 start() 方法;
  • RUNNABLE(運(yùn)行狀態(tài)):由 READY(就緒狀態(tài))和 RUNNING (運(yùn)行中)組成;
    • READY(就緒狀態(tài)):當(dāng)線程調(diào)用 start() 方法,程序進(jìn)入 READY(就緒狀態(tài))等待線程調(diào)度器選中執(zhí)行,還能通過以下方法進(jìn)入 READY(就緒狀態(tài));
      • Thread.yield() 當(dāng)前線程暫時(shí)放棄 CPU 搶占權(quán);
      • Thread.sleep()、Object.join()、Object.wait() 當(dāng)前線程掛起,掛起的線程進(jìn)入等待隊(duì)列,等待時(shí)間結(jié)束或被喚醒;
      • 當(dāng)前線程獲取到鎖;
    • RUNNING(運(yùn)行中):當(dāng)前線程被線程調(diào)度器執(zhí)行,這也是線程進(jìn)入該狀態(tài)的唯一方法;
  • TIMED_WAITING(超時(shí)等待):線程處于一定時(shí)間后自動(dòng)被喚醒;
  • WAITING(等待狀態(tài)):線程等待被喚醒;
  • BLOCKED(阻塞狀態(tài)):當(dāng)前線程進(jìn)入同步代碼塊,未獲得鎖對(duì)象的線程,進(jìn)入該鎖對(duì)象的同步隊(duì)列,等待搶鎖;
  • TERMINATED(終止?fàn)顟B(tài)):當(dāng)前線程執(zhí)行完成;

2.2 優(yōu)先級(jí)與守護(hù)線程

優(yōu)先級(jí)代表了線程被線程調(diào)度器選中執(zhí)行的機(jī)會(huì)大小,優(yōu)先級(jí)高的可能先執(zhí)行,優(yōu)先級(jí)低的可能后執(zhí)行。在 Java 中,線程優(yōu)先級(jí)被分為 1 到 10, 默認(rèn)優(yōu)先級(jí)為 5。在 Thread 類中定義如下,Java 中對(duì)線程所設(shè)置的優(yōu)先級(jí)只是給操作系統(tǒng)的一個(gè)建議,而真正調(diào)用順序,是由操作系統(tǒng)的線程調(diào)度算法完成決定的。


/**
 * 線程最小優(yōu)先級(jí)
 */
public static final int MIN_PRIORITY = 1;

/**
 * 線程默認(rèn)優(yōu)先級(jí)
 */
public static final int NORM_PRIORITY = 5;

/**
 * 線程最大優(yōu)先級(jí)
 */
public static final int MAX_PRIORITY = 10;
守護(hù)線程

在創(chuàng)建線程時(shí),默認(rèn)都是非守護(hù)線程,要?jiǎng)?chuàng)建守護(hù)線程需要將 daemon 屬性設(shè)置為 true。守護(hù)線程的優(yōu)先級(jí)很低,JVM 在退出時(shí),不會(huì)關(guān)注有無守護(hù)線程,JVM 仍然會(huì)退出,一般守護(hù)線程用于監(jiān)控的工作。當(dāng)線程已經(jīng)啟動(dòng)時(shí),如果調(diào)用 setDaemon 方法將其設(shè)置為守護(hù)線程,將會(huì)拋出 IllegalThreadStateException 異常。在守護(hù)線程中創(chuàng)建的線程仍然是守護(hù)線程。

2.3 構(gòu)建一個(gè)線程

Thread 類提供了多種構(gòu)造方法

public Thread() {
    this(null, null, "Thread-" + nextThreadNum(), 0);
}
    
public Thread(Runnable target) {
    this(null, target, "Thread-" + nextThreadNum(), 0);
}
    
/**
 * 創(chuàng)建一個(gè)繼承給定 AccessControlContext 的新線程,但不繼承線程局部變量
 */
Thread(Runnable target, AccessControlContext acc) {
    this(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
    
public Thread(ThreadGroup group, Runnable target) {
    this(group, target, "Thread-" + nextThreadNum(), 0);
}
    
public Thread(String name) {
    this(null, null, name, 0);
}
    
public Thread(ThreadGroup group, String name) {
    this(group, null, name, 0);
}
    
public Thread(Runnable target, String name) {
    this(null, target, name, 0);
}
    
public Thread(ThreadGroup group, Runnable target, String name) {
    this(group, target, name, 0);
}
    
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
    this(group, target, name, stackSize, null, true);
}
    
public Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) {
    this(group, target, name, stackSize, null, inheritThreadLocals);
}

以上構(gòu)造方法底層均調(diào)用一個(gè)初始化方法,在 JDK 1.8 中為 init 方法,在 JDK 11 中為私有的 Thread 構(gòu)造方法,兩個(gè) JDK 版本的實(shí)現(xiàn)大致相似,以下源碼以 JDK 11 為準(zhǔn)

/**
  * 初始化一個(gè)線程
  * ThreadGroup g 線程組,線程組可以對(duì)組內(nèi)的線程進(jìn)行批量操作,比如批量打斷 interrupt
  * Runnable target 要運(yùn)行的對(duì)象
  * String name 線程名稱,默認(rèn)使用 "Thread-" + nextThreadNum(),nextThreadNum 方法返回自增的數(shù)字
  * long stackSize 設(shè)置線程堆棧的大小,如果為 0 表示忽略次參數(shù)
  * AccessControlContext acc 要繼承的 AccessControlContext,如果為 null,則為 AccessController.getContext()
  * boolean inheritThreadLocals 是否繼承 ThreadLocal 中的初始值
  */
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
    // 設(shè)置線程的名稱
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    
    // 獲取當(dāng)前線程作為父線程
    Thread parent = currentThread();
    
    /**
     * 獲取系統(tǒng)安全管理器,安全管理器是一個(gè)允許應(yīng)用程序?qū)崿F(xiàn)安全策略的類
     * 它允許應(yīng)用程序在執(zhí)行可能不安全或敏感操作之前確認(rèn)該操作是什么以及是否允許執(zhí)行該操作的安全上下文中嘗試該操作
     */
    SecurityManager security = System.getSecurityManager();
    
    // 設(shè)置線程組
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
    
    // 判斷當(dāng)前線程是否有權(quán)限修改該線程組
    g.checkAccess();
    
    //
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    // 增加線程組中未啟動(dòng)線程的計(jì)數(shù)
    g.addUnstarted();
    
    // 子線程繼承線程組
    this.group = g;
    // 子線程繼承父線程的守護(hù)屬性
    this.daemon = parent.isDaemon();
    // 子線程繼承父線程的優(yōu)先級(jí)屬性
    this.priority = parent.getPriority();
    
    // 繼承父線程的 ClassLoader
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    
    // 是否繼承的 AccessControlContext
    this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
    
    // 設(shè)置該線程的執(zhí)行對(duì)象
    this.target = target;
    // 更改該線程的優(yōu)先級(jí)
    setPriority(priority);
    
    // 當(dāng) inheritThreadLocals 為 true 或者 父線程的 inheritableThreadLocals 不為空時(shí),會(huì)將父線程中所有的 inheritableThreadLocals 傳遞給子線程
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
    // 指定線程棧大小
    this.stackSize = stackSize;
    // 設(shè)置線程ID
    this.tid = nextThreadID();
}
StackSize

StackSize 參數(shù)是指定 JVM 為該線程分配線程棧大小,其參數(shù)的效果高度依賴于平臺(tái)。JVM 可以自由的將 StackSize 視為建議,如果指定值過低,JVM 可能會(huì)更改為使用某些特定與平臺(tái)的最小值;如果指定的值過高,JVM 可能會(huì)改為使用某個(gè)特定于平臺(tái)的最大值。

2.4 啟動(dòng)一個(gè)線程

實(shí)例化一個(gè) Thread 類后,需要調(diào)用 start 方法才是真正開啟了一個(gè)線程,如果只是調(diào)用了其 run 方法,只是對(duì)其進(jìn)行了一個(gè)方法調(diào)用。由于線程的狀態(tài)是不可逆的,因此一個(gè)線程調(diào)用 start 方法后,該線程的狀態(tài)將會(huì)變?yōu)?RUNNABLE,如果多次調(diào)用 start 方法,該線程將拋出 IllegalThreadStateException 異常。

/**
  * 開始執(zhí)行一個(gè)線程
  */
public synchronized void start() {
    // 判斷線程狀態(tài)如果不為 NEW ,則拋出異常
    if (threadStatus != 0) throw new IllegalThreadStateException();
    
    // 通知線程組該線程即將開啟,并將線程組中未啟動(dòng)線程計(jì)數(shù)遞減
    group.add(this);
    
    // 線程啟動(dòng)標(biāo)識(shí)
    boolean started = false;
    try {
        // 調(diào)用本地方法,真正開啟線程執(zhí)行,并將線程啟動(dòng)標(biāo)識(shí)設(shè)置為 ture
        start0();
        started = true;
    } finally {
        try {
            // 如果發(fā)生異常,通知線程組該線程嘗試啟動(dòng)失敗,將線程組的狀態(tài)進(jìn)行回滾
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                 it will be passed up the call stack */
        }
    }
}

/**
  * Java 本地方法
  */
private native void start0();

2.5 線程中常用的方法

  • wait 方法,調(diào)用該方法的線程進(jìn)入 WAITING 狀態(tài),如果指定了超時(shí)時(shí)間,該線程進(jìn)入 TIMED_WAITING 狀態(tài),并且進(jìn)入等待此對(duì)象的等待鎖定池,,只有等待其他線程通知喚醒或中斷才會(huì)轉(zhuǎn)為 RUNNABLE 狀態(tài),需要注意 調(diào)用 wait() 方法后,會(huì)釋放對(duì)象的鎖,因此,wait 方法一般用在同步方法或同步代碼塊中;
  • sleep 方法,調(diào)用該方法的線程進(jìn)入 TIMED_WAITING 狀態(tài), sleep 方法不會(huì)釋放當(dāng)前占有的鎖;
  • join 方法,等待指定線程終止,join 方法的本質(zhì)上就是調(diào)用 wait 方法;
  • yield 方法,調(diào)用該方法的線程將讓出 CPU 執(zhí)行時(shí)間片,與其他線程一起重新競(jìng)爭(zhēng) CPU 執(zhí)行時(shí)間片;
  • interrupt 方法,中斷此線程,并不是真的中斷此線程,而是設(shè)置標(biāo)志位(中斷位)來通知線程,如果此線程調(diào)用了 wait、joinsleep,則該線程的中斷狀態(tài)將被清除并拋出 InterruptedException,實(shí)際上只有聲明了會(huì)拋出 InterruptedException 的函數(shù)才會(huì)拋出異常,調(diào)用 interrupt 方法并不會(huì)拋出 InterruptedException;t.isInterrupted 和 Thread.interrupted 的區(qū)別在于,前者只用于讀取中斷狀態(tài),后者不僅讀取中斷狀態(tài),還會(huì)重置中斷標(biāo)志位;
  • stop 和 destory 方法都是用來強(qiáng)制關(guān)閉線程的,但是官方并不建議使用,因?yàn)閺?qiáng)制關(guān)閉線程,將導(dǎo)致線程中使用的資源,如:文件描述符、網(wǎng)絡(luò)資源等不能正常關(guān)閉。最好的方式應(yīng)當(dāng)?shù)却€程執(zhí)行完成,并完整的釋放資源,如果是一個(gè)不斷循環(huán)的線程,應(yīng)當(dāng)使用線程之間的通信,讓主線程通知其關(guān)閉;

三、ThreadGroup 類解析

線程組是線程的集合,其結(jié)構(gòu)類似于樹形結(jié)構(gòu),在樹中,除了初始線程組外,其他線程組都有一個(gè)父線程組。一個(gè)線程可以訪問當(dāng)前線程組信息,但是不能訪問其線程組的父級(jí)線程組信息或其他線程組信息。

3.1 構(gòu)建一個(gè)線程

線程組提供了兩種創(chuàng)建方式。

/**
 * 創(chuàng)建指定名稱的線程組,其父級(jí)線程組為當(dāng)前線程的線程組
 */
public ThreadGroup(String name) {
    this(Thread.currentThread().getThreadGroup(), name);
}

/**
 * 創(chuàng)建指定 父級(jí)線程組 和 名稱 的線程組
 */
public ThreadGroup(ThreadGroup parent, String name) {
    this(checkParentAccess(parent), parent, name);
}

以上構(gòu)造方法均調(diào)用線程組私有構(gòu)造方法,對(duì)于一個(gè)線程組,它也擁有自己的名字、優(yōu)先級(jí)、是否為守護(hù)線程組,并通過 ThreadGroup parent 記錄其父級(jí)線程組,ThreadGroup groups[] 記錄其線程組下的子線程組。

/**
 * 創(chuàng)建指定 父級(jí)線程組 和 名稱 的線程組
 */
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
    // 線程組名稱
    this.name = name;
    // 線程組的最大優(yōu)先級(jí),表示組內(nèi)所有的線程都不允許超過這個(gè)數(shù)值
    this.maxPriority = parent.maxPriority;
    // 線程組是否為守護(hù)線程組
    this.daemon = parent.daemon;
    // 當(dāng)前線程組的父級(jí)線程組
    this.parent = parent;
    // 將當(dāng)前線程組加入到 父級(jí)線程組中
    parent.add(this);
}

另外 ThreadGroup 內(nèi)部還有個(gè)私有的默認(rèn)構(gòu)造方法,用于創(chuàng)建系統(tǒng)級(jí)線程組。

/**
 * 創(chuàng)建一個(gè)不在任何線程組中的空線程組,該方法用于創(chuàng)建系統(tǒng)線程組
 */
private ThreadGroup() {
    this.name = "system";
    this.maxPriority = Thread.MAX_PRIORITY;
    this.parent = null;
}

3.2 ThreadGroup 常用的方法

  • activeCount 方法,返回此線程組及其子組中活動(dòng)線程數(shù)的估計(jì)值,因?yàn)樵摲椒▋?nèi)部使用遍歷內(nèi)部數(shù)據(jù),線程的數(shù)量可能會(huì)動(dòng)態(tài)發(fā)生變化,因此該方法返回的是估計(jì)值;
  • activeGroupCount 方法,返回此線程組及其子組中活動(dòng)組的數(shù)目的估計(jì)值;
  • checkAccess 方法,判斷當(dāng)前運(yùn)行的線程是否有權(quán)限修改當(dāng)前線程組,如果不能修改則拋出 SecurityException 異常;
  • destroy 方法,銷毀當(dāng)前線程組及其子組,調(diào)用該方法時(shí),需要確保當(dāng)前線程組為空,且當(dāng)前線程組中的線程全部完成。如果線程組不為空或線程組已被銷毀,將拋出 IllegalThreadStateException 異常;
  • enumerate 方法,將此線程組及其子組中的每個(gè)活動(dòng)線程復(fù)制到指定的數(shù)組中,如果 recurse 為 ture ,則此方法遞歸枚舉此線程組的所有子組,并且還包括對(duì)這些子組中每個(gè)活動(dòng)線程的應(yīng)用;
  • interrupt 方法,中斷此線程組及其子組中的所有線程;
  • list 方法,將有關(guān)此線程組的信息打印到標(biāo)準(zhǔn)輸出;
  • parentOf 方法,判斷當(dāng)前線程組是否為傳入線程組的祖先級(jí)線程組;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容