Java多線程的原理和用法

本篇內(nèi)容主要講解“Java多線程的原理和用法”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java多線程的原理和用法”吧!

成都創(chuàng)新互聯(lián)是一家專業(yè)提供江南企業(yè)網(wǎng)站建設,專注與成都做網(wǎng)站、成都網(wǎng)站制作、成都h5網(wǎng)站建設、小程序制作等業(yè)務。10年已為江南眾多企業(yè)、政府機構等服務。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設計公司優(yōu)惠進行中。

摘要:多線程可以理解為在同一個程序中能夠同時運行多個不同的線程來執(zhí)行不同的任務,這些線程可以同時利用CPU的多個核心運行。

多線程可以理解為在同一個程序中能夠同時運行多個不同的線程來執(zhí)行不同的任務,這些線程可以同時利用CPU的多個核心運行。多線程編程能夠最大限度的利用CPU的資源。本文將通過以下幾個方向為大家講解多線程的用法。

  • 1.Thread類基礎

  • 2.synchronized關鍵字

  • 3.其他的同步工具

  1. CountDownLatch

  2. FutureTask

  3. Semaphore

  4. CyclicBarrier

  5. Exchanger

  6. 原子類AtomicXXX

  • 4.線程池

  • 5.Thread狀態(tài)轉換

  • 6.Volatile

  • 7.線程群組

一、Thread類基礎

Q: Thread的deprecated過期方法是哪3個?作用是啥
A:

  • stop(), 終止線程的執(zhí)行。

  • suspend(), 暫停線程執(zhí)行。

  • resume(), 恢復線程執(zhí)行。

Q: 廢棄stop的原因是啥?
A:調(diào)用stop時,會直接終止線程并釋放線程上已鎖定的鎖,線程內(nèi)部無法感知,并且不會做線程內(nèi)的catch操作!即線程內(nèi)部不會處理stop后的爛攤子。如果其他線程等在等著上面的鎖去取數(shù)據(jù), 那么拿到的可能是1個半成品。

變成題目的話應該是下面這樣,問會輸出什么?

public class Test {

    public static void main(String[] args) throws InterruptedException {

        System.out.println("start");
        Thread thread = new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.stop();
        // thread.interrupt();

    }
}

class MyThread extends Thread {
    public void run() {
        try {
            System.out.println("run");
            Thread.sleep(5000);
        } catch (Exception e) {
            //處理爛攤子,清理資源
            System.out.println("clear resource!");
        }
    }
}

答案是輸出 start和run,但是不會輸出clear resource

Q: stop的替代方法是什么?
A: interrupt()。
調(diào)用thread.interrupt()終止時, 不會直接釋放鎖,可通過調(diào)用interrupt()或者捕捉sleep產(chǎn)生的中斷異常,來判斷是否被終止,并處理爛攤子。

上題把thread.stop()改成thread.interrupt(),在Thread.sleep()過程中就會拋出interrupException(注意,InterrupExcetpion是sleep拋出的)因此就會輸出clear resource。如果沒有做sleep操作, 可以用isInterrupted()來判斷自己這個線程是否被終止了,來做清理。

另外注意一下interrupt和isInterrupted的區(qū)別:

Java多線程的原理和用法

Q: suspend/resume的廢棄原因是什么?
A: :調(diào)用suspend不會釋放鎖。
如果線程A暫停后,他的resume是由線程B來調(diào)用的,但是線程B又依賴A里的某個鎖,那么就死鎖了。例如下面這個例子,就要知道會引發(fā)死鎖:

public class Test {
    public static Object lockObject = new Object();
    public static void main(String[] args) throws InterruptedException {

        System.out.println("start");
        Thread thread = new MyThread();
        thread.start();
        Thread.sleep(1000);

        System.out.println("主線程試圖占用lockObject鎖資源");
        synchronized (Test.lockObject) {
            // 用Test.lockObject做一些事
            System.out.println("做一些事");
        }
        System.out.println("恢復");
        thread.resume();

    }
}

class MyThread extends Thread {
    public void run() {
        try {
            synchronized (Test.lockObject) {
                System.out.println("占用Test.lockObject");
                suspend();
            }
            System.out.println("MyThread釋放TestlockObject鎖資源");
        }
        catch (Exception e){}
    }
}

答案輸出

Java多線程的原理和用法

MyThread內(nèi)部暫停后,外部的main因為沒法拿到鎖,所以無法執(zhí)行后面的resume操作。

Q: 上題的suspend和resume可以怎么替換,來解決死鎖問題?
A: 可以用wait和noitfy來處理(不過盡量不要這樣設計,一般都是用run內(nèi)部帶1個while循環(huán)的)

public class Test {
    public static Object lockObject = new Object(); //拿來做臨時鎖對象
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new MyThread();
        thread.start();
        Thread.sleep(1000);

        System.out.println("主線程試圖占用lockObject鎖資源");
        synchronized (Test.lockObject) {
            // 用Test.lockObject做一些事
            System.out.println("做一些事");
        }
        System.out.println("恢復");

        synchronized (Test.lockObject) {
            Test.lockObject.notify();
        }

    }
}

class MyThread extends Thread {
    public void run() {
        try {
            synchronized (Test.lockObject) {
                System.out.println("占用Test.lockObject");
                Test.lockObject.wait();
            }
            System.out.println("MyThread釋放TestlockObject鎖資源");
        }
        catch (Exception e){}
    }
}

如此執(zhí)行,結果正常:

Java多線程的原理和用法

Q: 下面這例子為什么會運行異常,拋出IllegalMonitorStateException錯誤?

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        thread.notify();
    }

A: notify和wait的使用前提是必須持有這個對象的鎖, 即main代碼塊 需要先持有thread對象的鎖,才能使用notify去喚醒(wait同理)。

改成下面就行了:

        Thread thread = new MyThread();
        thread.start();
        synchronized (thread) {
            thread.notify();
        }

Q: Thread.sleep()和Object.wait()的區(qū)別
A:sleep不會釋放對象鎖, 而wait會釋放對象鎖。

Q:Runnable接口和Callable的區(qū)別。
A: Callable可以和Futrue配合,并且啟動線程時用的時call,能夠拿到線程結束后的返回值,call方法還能拋出異常。

Q:thread.alive()表示線程當前是否處于活躍/可用狀態(tài)。
活躍狀態(tài): 線程已經(jīng)啟動且尚未終止。線程處于正在運行或準備開始運行的狀態(tài),就認為線程是“存活的

thread.start()后,是否alive()一定返回true?

public class Main {
    public static void main(String[] args) {
        TestThread tt = new TestThread();
        System.out.println("Begin == " + tt.isAlive());
        tt.start();
        System.out.println("end == " + tt.isAlive());
    }
}

A:不一定,有可能在打印時,線程已經(jīng)運行結束了,或者start后,還未真正啟動起來(就是還沒進入到run中)

Q: 線程A如下:

public class A extends Thread {
    @Override
    public void run() {
        System.out.println("this.isAlive()=" + this.isAlive());
    }
}

把線程A作為構造參數(shù),傳給線程B

A a = new A();
Thread b = new Thread(a);
b.start()

此時會打印什么?
A:此時會打印false!

Java多線程的原理和用法

因為把a作為構造參數(shù)傳入b中, b執(zhí)行start時, 實際上是在B線程中去調(diào)用了 A對象的run方法,而不是啟用了A線程。

如果改成

A a = new A();
a.start()

那么就會打印true了

Q:把FutureTask放進Thread中,并start后,會正常執(zhí)行callable里的內(nèi)容嗎?

public static void main(String[] args) throws Exception {
    Callable<Integer> callable = () -> {
    System.out.println("call 100");
    return 100;
    };
 
    FutureTask<Integer> task = new FutureTask<>(callable);
    Thread thread = new Thread(task);
    thread.start();
}

A:能正常打印

二、synchronized關鍵字

  • 即可作為方法的修飾符,也可以作為代碼塊的修飾符

  • 注意修飾方法時,并不是這個方法上有鎖, 而是調(diào)用該方法時,需要取該方法所在對象上的鎖。

class A{
     synchroized f(){
     }   
}

即調(diào)用這個f(), 并不是說f同一時刻只能進入一次,而是說進入f時,需要取到A上的鎖。

Q: 調(diào)用下面的f()時,會出現(xiàn)死鎖嗎?

class A{
     synchroized f(){
        t()
     }
 
     synchroized t(){
     }
}

A:不會。
1個線程內(nèi), 可以重復進入1個對象的synchroized 塊。

  • 原理:
    當線程請求自己的鎖時。JVM會記下鎖的持有者,并且給這個鎖計數(shù)為1。如果該線程再次請求自己的鎖,則可以再次進入,計數(shù)為2。退出時計數(shù)-1,直到全部退出時才會釋放鎖。

Q:2個線程同時調(diào)用f1和f2會產(chǎn)生同步嗎?

class A{
	private static synchronized void f1(){};
	private synchronized void f2(){};
}

A:不會產(chǎn)生同步。二者不是1個鎖。
f1是類鎖,等同于synchronized(A.class)
f2是對象鎖。

三、其他的同步工具

CountDownLatch

final CountDownLatch latch = new CountDownLatch(2);

2是計數(shù)器初始值。

然后執(zhí)行l(wèi)atch.await()時, 就會阻塞,直到其他線程中把這個latch進行l(wèi)atch.countDown(),并且計數(shù)器降低至0。

  • 和join的區(qū)別:
    join阻塞時,是只等待單個線程的完成
    而CountDownLatch可能是為了等待多個線程

Q: countDownLatch的內(nèi)部計數(shù)值能被重置嗎?
A:不能重置了。如果要重新計數(shù)必須重新new一個。畢竟他的類名就叫DownLatch

FutureTask

可以理解為一個支持有返回值的線程
FutureTask<Integer> task = new FutureTask<>(runable);
當調(diào)用task.get()時,就能能達到線程里的返回值

Q:調(diào)用futrueTask.get()時,這個是阻塞方法嗎?如果是阻塞,什么時候會結束?
A:是阻塞方法。

  1. 線程跑完并返回結果

  2. 阻塞時間達到futrueTask.get(xxx)里設定的xxx時間

  3. 線程出現(xiàn)異常InterruptedException或者ExecutionException

  4. 線程被取消,拋出CancellationException

Semaphore

信號量:就是操作系統(tǒng)里常見的那個概念,java實現(xiàn),用于各線程間進行資源協(xié)調(diào)。
用Semaphore(permits)構造一個包含permits個資源的信號量,然后某線程做了消費動作, 則執(zhí)行semaphore.acquire(),則會消費一個資源,如果某線程做了生產(chǎn)動作,則執(zhí)行semaphore.release(),則會釋放一個資源(即新增一個資源)

更詳細的信號量方法說明: https://blog.csdn.net/hanchao5272/article/details/79780045

Q: 信號量中,公平模式和非公平模式的區(qū)別?下面設成true就是公平模式

//new Semaphore(permits,fair):初始化許可證數(shù)量和是否公平模式的構造函數(shù)
semaphore = new Semaphore(5, true);

A:其實就是使用哪種公平鎖還是非公平鎖。

Java多線程的原理和用法

Java并發(fā)中的fairSync和NonfairSync主要區(qū)別為:

  • 如果當前線程不是鎖的占有者,則NonfairSync并不判斷是否有等待隊列,直接使用compareAndSwap去進行鎖的占用,即誰正好搶到,就給誰用!

  • 如果當前線程不是鎖的占有者,則FairSync則會判斷當前是否有等待隊列,如果有則將自己加到等待隊列尾,即嚴格的先到先得!

CyclicBarrier

柵欄,一般是在線程中去調(diào)用的。它的構造需要指定1個線程數(shù)量,和柵欄被破壞前要執(zhí)行的操作,每當有1個線程調(diào)用barrier.await(),就會進入阻塞,同時barrier里的線程計數(shù)-1。
當線程計數(shù)為0時, 調(diào)用柵欄里指定的那個操作后,然后破壞柵欄, 所有被阻塞在await上的線程繼續(xù)往下走。

Exchanger

我理解為兩方柵欄,用于交換數(shù)據(jù)。
簡單說就是一個線程在完成一定的事務后,想與另一個線程交換數(shù)據(jù),則第一個先拿出數(shù)據(jù)的線程會一直等待第二個線程,直到第二個線程拿著數(shù)據(jù)到來時才能彼此交換對應數(shù)據(jù)。

原子類AtomicXXX

就是內(nèi)部已實現(xiàn)了原子同步機制
Q:下面輸出什么?(考察getAndAdd的用法)

AtomicInteger num = new AtomicInteger(1);
System.out.println(num.getAndAdd(1));
System.out.println(num.get());

A:輸出1、2
顧名思義, getAndAdd(),那么就是先get,再加, 類似于num++。
如果是addAndGet(),那么就是++num

Q:AtomicReference和AtomicInteger的區(qū)別?
A:AtomicInteger是對整數(shù)的封裝,而AtomicReference則對應普通的對象引用。也就是它可以保證你在修改對象引用時的線程安全性。即可能會有多個線程修改atomicReference里包含的引用。

  • 經(jīng)典用法:
    boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference)就是經(jīng)典的CAS同步法
    compreAndSet它會將將引用與預期值(引用)進行比較,如果它們相等,則在AtomicReference對象內(nèi)設置一個新的引用。類似于一個非負責的自旋鎖。

  • AtomicReferenceArray是原子數(shù)組, 可以進行一些原子的數(shù)組操作例如 set(index, value),

java中已實現(xiàn)的全部原子類:

Java多線程的原理和用法

注意,沒有float,沒有short和byte。

四、線程池

Q: ThreadPoolExecutor線程池構造參數(shù)中,corePoolSize和maximumPoolSize有什么區(qū)別?
A:當提交新線程到池中時

  • 如果當前線程數(shù) < corePoolSize,則會創(chuàng)建新線程

  • 如果當前線程數(shù)=corePoolSize,則新線程被塞進一個隊列中等待。

  • 如果隊列也被塞滿了,那么又會開始新建線程來運行任務,避免任務阻塞或者丟棄

  • 如果隊列滿了的情況下, 線程總數(shù)超過了maxinumPoolSize,那么就拋異?;蛘咦枞ㄈQ于隊列性質(zhì))。

  • 調(diào)用prestartCoreThread()可提前開啟一個空閑的核心線程

  • 調(diào)用prestartAllCoreThreads(),可提前創(chuàng)建corePoolSize個核心線程。

Q: 線程池的keepalive參數(shù)是干嘛的?
A:當線程數(shù)量在corePoolSize到maxinumPoolSize之間時, 如果有線程已跑完,且空閑時間超過keepalive時,則會被清除(注意只限于corePoolSize到maxinumPoolsize之間的線程)

Q: 線程池有哪三種隊列策略?
A:

  1. 握手隊列
    相當于不排隊的隊列??赡茉斐删€程數(shù)量無限增長直到超過maxinumPoolSize(相當于corePoolSize沒什么用了,只以maxinumPoolSize做上限)

  2. 無界隊列
    隊列隊長無限,即線程數(shù)量達到corePoolSize時,后面的線程只會在隊列中等待。(相當于maxinumPoolSize沒什么用了)
    缺陷: 可能造成隊列無限增長以至于OOM

  3. 有界隊列

Q: 線程池隊列已滿且maxinumPoolSize已滿時,有哪些拒絕策略?
A:

  • AbortPolicy 默認策略:直接拋出RejectedExecutionException異常

  • DiscardPolicy 丟棄策略: 直接丟了,什么錯誤也不報

  • DiscardOldestPolicy 丟棄隊頭策略: 即把最先入隊的人從隊頭扔出去,再嘗試讓該任務進入隊尾(隊頭任務內(nèi)心:不公平。。。。)

  • CallerRunsPolicy 調(diào)用者處理策略: 交給調(diào)用者所在線程自己去跑任務(即誰調(diào)用的submit或者execute,他就自己去跑)

  • 也可以用實現(xiàn)自定義新的RejectedExecutionHandler

Q:有以下五種Executor提供的線程池,注意記憶一下他們的用途,就能理解內(nèi)部的原理了。

  • newCachedThreadPool: 緩存線程池
    corePoolSize=0, maxinumPoolSize=+∞,隊列長度=0 ,因此線程數(shù)量會在corePoolSize到maxinumPoolSize之間一直靈活緩存和變動, 且不存在隊列等待的情況,一來任務我就創(chuàng)建,用完了會釋放。

Java多線程的原理和用法

  • newFixedThreadPool :定長線程池
    corePoolSize= maxinumPoolSize=構造參數(shù)值, 隊列長度=+∞。因此不存在線程不夠時擴充的情況

  • newScheduledThreadPool :定時器線程池
    提交定時任務用的,構造參數(shù)里會帶定時器的間隔和單位。 其他和FixedThreadPool相同,屬于定長線程池。

  • newSingleThreadExecutor : 單線程池
    corePoolSize=maxinumPoolSize=1, 隊列長度=+∞,只會跑一個任務, 所以其他的任務都會在隊列中等待,因此會嚴格按照FIFO執(zhí)行

  • newWorkStealingPool(繼承自ForkJoinPool ): 并行線程池
    如果你的任務執(zhí)行時間很長,并且里面的任務運行并行跑的,那么他會把你的線程任務再細分到其他的線程來分治。ForkJoinPool介紹: https://blog.csdn.net/m0_37542889/article/details/92640903

Q: submit和execute的區(qū)別是什么?
A:

  • execute只能接收Runnable類型的任務,而submit除了Runnable,還能接收Callable(Callable類型任務支持返回值)

  • execute方法返回void, submit方法返回FutureTask。

  • 異常方面, submit方法因為返回了futureTask對象,而當進行future.get()時,會把線程中的異常拋出,因此調(diào)用者可以方便地處理異常。(如果是execute,只能用內(nèi)部捕捉或者設置catchHandler)

Q:線程池中, shutdown、 shutdownNow、awaitTermination的區(qū)別?
A:

  • shutdown: 停止接收新任務,等待所有池中已存在任務完成( 包括等待隊列中的線程 )。異步方法,即調(diào)用后馬上返回。

  • shutdownNow: 停止接收新任務,并 停止所有正執(zhí)行的task,返回還在隊列中的task列表 。

  • awaitTermination: 僅僅是一個判斷方法,判斷當前線程池任務是否全部結束。一般用在shutdown后面,因為shutdown是異步方法,你需要知道什么時候才真正結束。

五、Thread狀態(tài)轉換

Q: 線程的6種狀態(tài)是:
A:

  • New: 新建了線程,但是還沒調(diào)用start

  • RUNNABLE: 運行, 就緒狀態(tài)包括在運行態(tài)中

  • BLOCKED: 阻塞,一般是因為想拿鎖拿不到

  • WAITING: 等待,一般是wait或者join之后

  • TIMED_WAITING: 定時等待,即固定時間后可返回,一般是調(diào)用sleep或者wait(時間)的。

  • TERMINATED: 終止狀態(tài)。

欣賞一幅好圖,能了解調(diào)用哪些方法會進入哪些狀態(tài)。

Java多線程的原理和用法

原圖鏈接
Q: java線程什么時候會進入阻塞(可能按多選題考):
A:

  • sleep

  • wati()掛起, 等待獲得別的線程發(fā)送的Notify()消息

  • 等待IO

  • 等待鎖

六、Volatile

用volatile修飾成員變量時, 一旦有線程修改了變量,其他線程可立即看到改變。

Q: 不用volatile修飾成員變量時, 為什么其他線程會無法立即看到改變?
A:線程可以把變量保存在本地內(nèi)存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。
這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續(xù)使用它在寄存器中的變量值。

Q: 用了volatile是不是就可以不用加鎖啦?
A: 不行。

  • 鎖并不是只保證1個變量的互斥, 有時候是要保證幾個成員在連續(xù)變化時,讓其他線程無法干擾、讀取。

  • 而volatile保證1個變量可變, 保證不了幾個變量同時變化時的原子性。

Q:展示一段《Java并發(fā)編程實戰(zhàn)》書里的一個經(jīng)典例子,在科目二考試里也出現(xiàn)了,只是例子換了個皮。為什么下面這個例子可能會死循環(huán),或者輸出0?

Java多線程的原理和用法

A:首先理解一下java重排序,可以看一下這篇博文: https://www.cnblogs.com/coshaho/p/8093944.html

然后分析后面那2個奇怪的情況是怎么發(fā)生的。

  • 永遠不輸出:
    經(jīng)過程序的指令排序,出現(xiàn)了這種情況:

  1. ReaderThread在while里讀取ready值, 此時是false, 于是存入了ReaderThread的寄存器。

  2. 主線程修改ready和number。

  3. ReaderThread沒有感知到ready的修改(對于ReaderThread線程,感知不到相關的指令,來讓他更新ready寄存器的值),因此進入死循環(huán)。

  • 輸出0
    經(jīng)過程序的指令排序,出現(xiàn)了這種情況:
    1)主線程設置ready為true
    2)ReaderThread在while里讀取ready值,是true,于是退出while循環(huán)

  1. ReaderThread讀取到number值, 此時number還是初始化的值為0,于是輸出0

  2. 主線程這時候才修改number=42,此時ReaderThread已經(jīng)結束了!

上面這個問題,可以用volatile或者加鎖。當你加了鎖時, 如果變量被寫了,會有指令去更新另一個寄存器的值,因此就可見了。

七、線程群組

為了方便管理一批線程,我們使用ThreadGroup來表示線程組,通過它對一批線程進行分類管理

使用方法:

Thread group = new ThreadGroup("group");
Thread thread = new Thread(gourp, ()->{..});

即thread除了Thread(Runable)這個構造方法外,還有個Thread(ThreadGroup, Runnable)構造方法

Q:在線程A中創(chuàng)建線程B, 他們屬于同一個線程組嗎
A:是的

線程組的一大作用是對同一個組線程進行統(tǒng)一的異常捕捉處理,避免每次新建線程時都要重新去setUncaghtExceptionHandler。即線程組自身可以實現(xiàn)一個uncaughtException方法。

ThreadGroup group = new ThreadGroup("group") {
	@Override
	public void uncaughtException(Thread thread, Throwable throwable) {
		System.out.println(thread.getName() + throwable.getMessage());
		}
	};
}

線程如果拋出異常,且沒有在線程內(nèi)部被捕捉,那么此時線程異常的處理順序是什么?相信很多人都看過下面這段話,好多講線程組的博客里都這樣寫:
(1)首先看看當前線程組(ThreadGroup)有沒有父類的線程組,如果有,則使用父類的UncaughtException()方法。
(2)如果沒有,就看線程是不是調(diào)用setUncaughtExceptionHandler()方法建立Thread.setUncaughtExceptionHandler實例。如果建立,直接使用它的UncaughtException()方法處理異常。
(3)如果上述都不成立就看這個異常是不是ThreadDead實例,如果是,什么都不做,如果不是,輸出堆棧追蹤信息(printStackTrace)。

來源:

https://blog.csdn.net/qq_43073128/article/details/90597006
https://blog.csdn.net/qq_43073128/article/details/88280469

好,別急著記,先看一下下面的題目,問輸出什么:
Q:

// 父類線程組
static class GroupFather extends ThreadGroup {
    public GroupFather(String name) {
        super(name);
    }
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        System.out.println("groupFather=" + throwable.getMessage());
    }
}

public static void main(String[] args) {
    // 子類線程組
    GroupFather groupSon = new GroupFather("groupSon") {
        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            System.out.println("groupSon=" + throwable.getMessage());
        }
    };
    Thread thread1 = new Thread(groupSon, ()->{
        throw new RuntimeException("我異常了");
    });
    thread1.start();
}

A:一看(1),那是不是應該輸出groupFather?

錯錯錯,輸出的是groupSon這句話在很多地方能看到,但沒有去實踐過看過源碼的人就會這句話被誤導。實際上父線程組不是指類繼承關系上的線程組,而是指下面這樣的:

Java多線程的原理和用法

即指的是構造關系的有父子關系。如果子類的threadGroup沒有去實現(xiàn)uncaughtException方法,那么就會去構造參數(shù)里指定的父線程組去調(diào)用方法。

Q: 那我改成構造關系上的父子關系,下面輸出什么?

public static void main(String[] args) {
    // 父線程組
    ThreadGroup groupFather = new ThreadGroup("groupFather") {
        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            System.out.println("groupFather=" + throwable.getMessage());
        }
    };

    // 子線程組,把groupFather作為parent參數(shù)
    ThreadGroup groupSon = new ThreadGroup(groupFather, "groupSon") {
        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            System.out.println("groupSon=" + throwable.getMessage());
        }
    };

    Thread thread1 = new Thread(groupSon, ()->{
        throw new RuntimeException("我異常了");
    });

    thread1.start();
}

A:答案輸出

Java多線程的原理和用法

即只要子線程組有實現(xiàn)過,則會用子線程組里的方法,而不是直接去找的父線程組!

Q:如果我讓自己做set捕捉器的操作呢?那下面這個輸出什么?

public static void main(String[] args) {
    // 父線程組
    ThreadGroup group = new ThreadGroup("group") {
        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            System.out.println("group=" + throwable.getMessage());
        }
    };

    // 建一個線程,在線程組內(nèi)
    Thread thread1 = new Thread(group, () -> {
        throw new RuntimeException("我異常了");
    });

    // 自己設置setUncaughtExceptionHandler方法
    thread1.setUncaughtExceptionHandler((t, e) -> {
        System.out.println("no gourp:" + e.getMessage());
    });

    thread1.start();
}

A:看之前的結論里,似乎是應該輸出線程組的異常?
但是結果卻輸出的是:

Java多線程的原理和用法

也就是說,如果線程對自己特地執(zhí)行過setUncaughtExceptionHandler,那么有優(yōu)先對自己設置過的UncaughtExceptionHandler做處理。

那難道第(2)點這個是錯的嗎?確實錯了,實際上第二點應該指的是全局Thread的默認捕捉器,注意是全局的。實際上那段話出自ThreadGroup里uncaughtException的源碼:

Java多線程的原理和用法

這里就解釋了之前的那三點,但是該代碼中沒考慮線程自身設置了捕捉器

所以修改一下之前的總結一下線程的實際異常拋出判斷邏輯:

  1. 如果線程自身有進行過setUncaughtExceptionHandler,則使用自己設置的按個。

  2. 如果沒設置過,則看一下沒有線程組。并按照以下邏輯判斷:
    如果線程組有覆寫過uncaughtException,則用覆寫過的uncaughtException
    如果線程組沒有覆寫過,則去找父線程組(注意是構造體上的概念)的uncaughtException方法。

  3. 如果線程組以及父類都沒覆寫過uncaughtException, 則判斷是否用Thread.setDefaultUncaughtExceptionHandler(xxx)去設置全局的默認捕捉器,有的話則用全局默認

  4. 如果不是ThreadDeath線程, 則只打印堆棧。

  5. 如果是ThreadDeath線程,那么就什么也不處理。

到此,相信大家對“Java多線程的原理和用法”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

當前標題:Java多線程的原理和用法
標題URL:http://www.muchs.cn/article10/iidjgo.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、網(wǎng)站營銷、品牌網(wǎng)站制作、軟件開發(fā)、網(wǎng)站導航、服務器托管

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設網(wǎng)站維護公司