国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java 多線程

zorro / 1102人閱讀

摘要:當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順序執(zhí)行流就是一個(gè)線程所有運(yùn)行中的任務(wù)通常對應(yīng)一個(gè)進(jìn)程。線程也被稱作輕量級進(jìn)程,線程是進(jìn)程的執(zhí)行單元。在線程的生命周期中,它要經(jīng)過新

線程概述 線程和進(jìn)程

幾乎所有的操作系統(tǒng)都支持同時(shí)運(yùn)行多個(gè)任務(wù),一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程。當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順序執(zhí)行流就是一個(gè)線程

所有運(yùn)行中的任務(wù)通常對應(yīng)一個(gè)進(jìn)程(Process)。當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程。進(jìn)程是處于運(yùn)行過程中的程序,并且具有一定的獨(dú)立功能。進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位

進(jìn)程包含如下3個(gè)特征:

獨(dú)立性:進(jìn)程是系統(tǒng)中獨(dú)立存在的實(shí)體,它擁有獨(dú)立的資源,每一個(gè)進(jìn)程都擁有自己私有的地址空間,沒有經(jīng)過進(jìn)程本身運(yùn)行的情況下,一個(gè)用戶進(jìn)程不可以訪問其他進(jìn)程的地址空間

動態(tài)性:進(jìn)程與程序的區(qū)別在于,程序只是一個(gè)靜態(tài)指令集合,而進(jìn)程是一個(gè)正在系統(tǒng)中活動的指令集合,在進(jìn)程中加入了時(shí)間概念

并發(fā)性:多個(gè)進(jìn)程可以在單個(gè)處理器上并發(fā)執(zhí)行,多個(gè)線程之間不會相互影響

并發(fā)性(concurrency)與并行性(parallel)

并發(fā):在同一時(shí)刻,只能有一條指令得到執(zhí)行,但多個(gè)進(jìn)程指令塊快速輪換執(zhí)行,使得在宏觀上具有多個(gè)進(jìn)程同時(shí)執(zhí)行的效果

并行:在同一時(shí)刻,有多條指令在多個(gè)處理器上同時(shí)執(zhí)行

多線程則是擴(kuò)展了多進(jìn)程的概念,使得同一個(gè)進(jìn)程可以同時(shí)并發(fā)處理多個(gè)任務(wù)。線程(Thread)也被稱作輕量級進(jìn)程(Lightweight Process),線程是進(jìn)程的執(zhí)行單元。就像進(jìn)程在操作系統(tǒng)中的地位一樣,線程在程序中是獨(dú)立的、并發(fā)的執(zhí)行流

線程是進(jìn)程的組成部分,一個(gè)進(jìn)程可以擁有多個(gè)線程,一個(gè)線程必須有一個(gè)父進(jìn)程。線程可以擁有自己的堆棧、自己的程序計(jì)數(shù)器和自己的局部變量,但不擁有系統(tǒng)資源,它與父進(jìn)程的其他線程共享該進(jìn)程所擁有的全部資源

線程可以完成一定的任務(wù),可以與其他線程共享父進(jìn)程中的共享變量及部分環(huán)境,相互之間協(xié)同來完成進(jìn)程所要完成的任務(wù)

線程是獨(dú)立運(yùn)行的,它并不知道進(jìn)程中是否還有其他線程存在。線程的執(zhí)行時(shí)搶占式的,也就是說,當(dāng)前運(yùn)行的線程在任何時(shí)候都可能被掛起,以便另外一個(gè)線程可以運(yùn)行

一個(gè)線程可以創(chuàng)建和撤銷另一個(gè)線程,同一個(gè)進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行

從邏輯角度來看,多線程存在于一個(gè)應(yīng)用程序中,讓一個(gè)應(yīng)用程序中可以有多個(gè)執(zhí)行部分同時(shí)執(zhí)行,但操作系統(tǒng)無須將多個(gè)線程看作多個(gè)獨(dú)立的應(yīng)用,對多線程實(shí)現(xiàn)調(diào)度和管理以及資源分配。線程的調(diào)度和管理由進(jìn)程本身負(fù)責(zé)完成

簡而言之,一個(gè)程序運(yùn)行過程中至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程中可以包含多個(gè)線程,但至少要包含一個(gè)線程

總結(jié):操作系統(tǒng)可以同時(shí)執(zhí)行多個(gè)任務(wù),每個(gè)任務(wù)就是進(jìn)程;進(jìn)程可以同時(shí)執(zhí)行多個(gè)任務(wù),每個(gè)任務(wù)就是線程

多線程的優(yōu)勢

線程在程序中是獨(dú)立的、并發(fā)的執(zhí)行流,與分隔的進(jìn)程相比,進(jìn)程中線程之間的隔離程度要小。它們共享內(nèi)存、文件句柄和其他每個(gè)進(jìn)程應(yīng)有的狀態(tài)

使用多線程編程具有如下優(yōu)點(diǎn):

進(jìn)程之間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易

系統(tǒng)創(chuàng)建進(jìn)程時(shí)需要為該進(jìn)程重新分配系統(tǒng)資源,但創(chuàng)建線程則代價(jià)小很多,因此使用多線程來實(shí)現(xiàn)多任務(wù)并發(fā)比多進(jìn)程的效率高

Java語言內(nèi)置了多線程功能的支持,而不是簡單的底層操作系統(tǒng)調(diào)度,從而簡化了多線程編程

線程的創(chuàng)建和啟動

Java使用Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實(shí)例

繼承Thread類創(chuàng)建線程類

通過繼承Thread類創(chuàng)建線程類步驟如下:

定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務(wù)。因此把run()方法稱為線程執(zhí)行體

創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建線程對象

調(diào)用線程對象的start()方法來啟動該線程

// 通過繼承Thread類來創(chuàng)建線程類
public class FirstThread extends Thread
{
    private int i ;
    // 重寫run方法,run方法的方法體就是線程執(zhí)行體
    public void run()
    {
        for ( ; i < 100 ; i++ )
        {
            // 當(dāng)線程類繼承Thread類時(shí),直接使用this即可獲取當(dāng)前線程
            // Thread對象的getName()返回當(dāng)前該線程的名字
            // 因此可以直接調(diào)用getName()方法返回當(dāng)前線程的名
            System.out.println(getName() +  " " + i);
        }
    }
    public static void main(String[] args)
    {
        for (int i = 0; i < 100;  i++)
        {
            // 調(diào)用Thread的currentThread方法獲取當(dāng)前線程
            System.out.println(Thread.currentThread().getName()
                +  " " + i);
            if (i == 20)
            {
                // 創(chuàng)建、并啟動第一條線程
                new FirstThread().start();
                // 創(chuàng)建、并啟動第二條線程
                new FirstThread().start();
            }
        }
    }
}

程序顯式地創(chuàng)建并啟動了2個(gè)線程,實(shí)際上程序有3個(gè)線程,即程序顯式創(chuàng)建的2個(gè)線程和主線程

Thread-0和Thread-1兩個(gè)線程輸出的i變量不連續(xù)。因?yàn)閕變量是FirstThread的實(shí)例變量,而不是局部變量,程序每次創(chuàng)建一個(gè)FirstThread對象,所以Thread-0和Thread-1不共享該實(shí)例變量

進(jìn)行多線程編程時(shí),不要忘記Java程序運(yùn)行時(shí)默認(rèn)的主線程,main()方法的方法體代表主線程的線程執(zhí)行體

Thread.currentThread():currentThread()是Thread類的靜態(tài)方法,該方法總是返回當(dāng)前正在執(zhí)行的線程對象

getName():該方法是Thread類的實(shí)例方法,該方法返回調(diào)用該方法的線程名字

setName():該方法設(shè)置線程的名字

默認(rèn)情況下,主線程的名字為main,用戶啟動的多個(gè)線程的名字依次被Thread-0、Thread-1、Thread-2、...、Thread-n等

使用繼承Thread類的方法來創(chuàng)建線程類時(shí),多個(gè)線程之間無法共享線程類的實(shí)例變量

實(shí)現(xiàn)Runnable接口創(chuàng)建線程類

實(shí)現(xiàn)Runnable接口創(chuàng)建線程類步驟如下:

定義Runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體

創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)現(xiàn)作為Thread的target來創(chuàng)建Thread對象,該對象才是線程的真正對象
Runnable對象僅僅作為Thread對象的target,Runnable實(shí)現(xiàn)類里包含的run()方法僅作為線程執(zhí)行體。實(shí)際的線程對象依然是Thread實(shí)例,只是該Thread線程負(fù)責(zé)執(zhí)行其target的run()方法

// 創(chuàng)建Runnable實(shí)現(xiàn)類的對象
ThreadTest tt = new ThreadTest();
// 以Runnable實(shí)現(xiàn)類的對象作為Thread的target來創(chuàng)建Thread對象,即線程對象
new Thread(tt);

調(diào)用該線程對象的start()方法來啟動線程

通過Thread獲取當(dāng)前線程對象比較簡單,直接通過this就可以,但是通過Runnable接口獲得當(dāng)前線程對象,則必須使用Thread.currentThread()

// 通過實(shí)現(xiàn)Runnable接口來創(chuàng)建線程類
public class SecondThread implements Runnable
{
    private int i ;
    // run方法同樣是線程執(zhí)行體
    public void run()
    {
        for ( ; i < 100 ; i++ )
        {
            // 當(dāng)線程類實(shí)現(xiàn)Runnable接口時(shí),
            // 如果想獲取當(dāng)前線程,只能用Thread.currentThread()方法。
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }

    public static void main(String[] args)
    {
        for (int i = 0; i < 100;  i++)
        {
            System.out.println(Thread.currentThread().getName() + "  " + i);
            if (i == 20)
            {
                SecondThread st = new SecondThread();
                // 通過new Thread(target, name)方法創(chuàng)建新線程
                new Thread(st , "新線程1").start();
                new Thread(st , "新線程2").start();
            }
        }
    }
}

采用Runnable接口的方式創(chuàng)建的多個(gè)線程可以共享線程類的實(shí)例變量。程序所創(chuàng)建的Runnable對象只是線程的target,所以多個(gè)線程可以共享同一個(gè)現(xiàn)場了(實(shí)際上應(yīng)該是線程的target類)的實(shí)例變量

使用Callable和Future創(chuàng)建線程

Callable接口類似Runnable接口的增強(qiáng)版,提供了一個(gè)call()方法可以作為線程執(zhí)行體,call()比run()方法更強(qiáng)大

call()可以有返回值

call()方法可以聲明拋出異常

Callable接口并不是Runnable接口的子接口,所以Callable接口不能直接做為Thread的target。而且call()方法還有一個(gè)返回值——call()方法不是直接調(diào)用,是作為線程執(zhí)行體被調(diào)用的

Future接口來代表Callable接口里call()方法里的返回值,F(xiàn)uture接口提供了FutureTask實(shí)現(xiàn)類,該接口實(shí)現(xiàn)了Future接口,并實(shí)現(xiàn)了Runnable接口,可以作為Thread類的target

在Future接口里定義了如下幾個(gè)公共方法來控制它關(guān)聯(lián)的Callable任務(wù):

boolean cancel(Boolean mayInterruptlfRunning):試圖取消該Future里關(guān)聯(lián)的Callable任務(wù)

V get():返回Callable任務(wù)里的call方法的返回值,調(diào)用該方法將導(dǎo)致線程阻塞,必須等到子線程結(jié)束才得到返回值

V get(long timeout, TimeUnit unit):返回Callable任務(wù)里的call方法的返回值,該方法讓程序最多阻塞timeout和unit指定的時(shí)間。如果經(jīng)過指定時(shí)間后Callable任務(wù)依然沒有返回值,將會拋出TimeoutException

boolean isCancelled:如果在Callable任務(wù)正常完成前被取消,則返回true

boolean isDone:如果Callable任務(wù)已經(jīng)完成,則返回true

Callable接口有泛型限制,Callable接口里的泛型形參類型與call()方法返回值類型相同。而且Callable接口是函數(shù)式接口,因此可以使用Lambda表達(dá)式創(chuàng)建Callable對象

創(chuàng)建并啟動有返回值的線程步驟如下:

創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該call()方法作為線程的執(zhí)行體,且有返回值,再創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例。從Java8開始,可以直接使用Lambda表達(dá)式創(chuàng)建Callable對象

使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值

使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程

調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值

import java.util.concurrent.*;

public class ThirdThread
{
    public static void main(String[] args)
    {
        // 創(chuàng)建Callable對象
        ThirdThread rt = new ThirdThread();
        // 先使用Lambda表達(dá)式創(chuàng)建Callable對象
        // 使用FutureTask來包裝Callable對象
        FutureTask task = new FutureTask((Callable)() -> {
            int i = 0;
            for ( ; i < 100 ; i++ )
            {
                System.out.println(Thread.currentThread().getName() + " 的循環(huán)變量i的值:" + i);
            }
            // call()方法可以有返回值
            return i;
        });
        for (int i = 0 ; i < 100 ; i++)
        {
            System.out.println(Thread.currentThread().getName() + " 的循環(huán)變量i的值:" + i);
            if (i == 20)
            {
                // 實(shí)質(zhì)還是以Callable對象來創(chuàng)建、并啟動線程
                new Thread(task , "有返回值的線程").start();
            }
        }
        try
        {
            // 獲取線程返回值
            System.out.println("子線程的返回值:" + task.get());
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
}

程序使用Lambda表達(dá)式直接創(chuàng)建Callable對象,這樣就無須先創(chuàng)建Callable實(shí)現(xiàn)類,再創(chuàng)建Callable對象

創(chuàng)建線程的三種方式對比

采用實(shí)現(xiàn)Runnable、Callable接口的方式創(chuàng)建多線程的優(yōu)缺點(diǎn):

線程類只是實(shí)現(xiàn)了Runnable接口或Callable,還可以繼承其他類

在這種方式下,多個(gè)線程可以共享同一個(gè)target對象,所以非常適合多個(gè)相同線程來處理同一份資源的情況,從而可以將CPU,代碼和數(shù)據(jù)分開,形成清晰的模型,較好地體現(xiàn)了面向?qū)ο蟮乃枷?/p>

劣勢是:編程稍稍復(fù)雜,如果需要訪問當(dāng)前線程,必須使用Thread.currentThread()方法

采用繼承Thread類的方式創(chuàng)建多線程的優(yōu)缺點(diǎn):

劣勢是:因?yàn)榫€程類已經(jīng)繼承了Thread類,所以不能再繼承其他父類

優(yōu)勢是:編寫簡單,如果需要訪問當(dāng)前線程,無需使用Thread.currentThread()方法,直接使用this即可獲得當(dāng)前線程

線程的生命周期

當(dāng)線程被創(chuàng)建并啟動以后,它既不是一啟動就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)。在線程的生命周期中,它要經(jīng)過新建(New)、就緒(Runnable)、運(yùn)行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態(tài)

新建和就緒狀態(tài)

當(dāng)程序使用new關(guān)鍵字創(chuàng)建了一個(gè)線程之后,該線程就處于新建狀態(tài),此時(shí)它和其他的Java對象一樣,僅僅由Java虛擬機(jī)為其分配內(nèi)存,并初始化其成員變量的值。此時(shí)的線程對象沒有表現(xiàn)出任何線程的動態(tài)特征,程序也不會執(zhí)行線程的線程執(zhí)行體

當(dāng)線程對象調(diào)用了start()方法之后,該線程處于就緒狀態(tài)。Java虛擬機(jī)會為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器,處于這個(gè)狀態(tài)中的線程并沒有開始運(yùn)行,只是表示該線程可以運(yùn)行了。至于該線程何時(shí)開始運(yùn)行,取決于JVM里線程調(diào)度器的調(diào)度

啟動線程使用start()方法,而不是run()方法。永遠(yuǎn)不要調(diào)用線程對象的run()方法。調(diào)用start()方法來啟動線程,系統(tǒng)會把該run()方法當(dāng)成線程執(zhí)行體來處理;但如果直按調(diào)用線程對象的run()方法,則run()方法立即就會被執(zhí)行,而且在run()方法返回之前其他線程無法并發(fā)執(zhí)行。也就是說,系統(tǒng)把線程對象當(dāng)成一個(gè)普通對象,而run()方法也是一個(gè)普通方法,而不是線程執(zhí)行體

public class InvokeRun extends Thread
{
    private int i ;
    // 重寫run方法,run方法的方法體就是線程執(zhí)行體
    public void run()
    {
        for ( ; i < 100 ; i++ )
        {
            // 直接調(diào)用run方法時(shí),Thread的this.getName返回的是該對象名字,
            // 而不是當(dāng)前線程的名字。
            // 使用Thread.currentThread().getName()總是獲取當(dāng)前線程名字
            System.out.println(Thread.currentThread().getName() +  " " + i);   // ①
        }
    }
    public static void main(String[] args)
    {
        for (int i = 0; i < 100;  i++)
        {
            // 調(diào)用Thread的currentThread方法獲取當(dāng)前線程
            System.out.println(Thread.currentThread().getName() +  " " + i);
            if (i == 20)
            {
                // 直接調(diào)用線程對象的run方法,
                // 系統(tǒng)會把線程對象當(dāng)成普通對象,run方法當(dāng)成普通方法,
                // 所以下面兩行代碼并不會啟動兩條線程,而是依次執(zhí)行兩個(gè)run方法
                new InvokeRun().run();
                new InvokeRun().run();
            }
        }
    }
}

程序運(yùn)行結(jié)果是整個(gè)程序只有一個(gè)線程:主線程。如果直接調(diào)用線程對象的run()方法,則run()方法里不能直接通過getName()方法來獲得當(dāng)前執(zhí)行線程的名字,而是需要Thread.currentThread()方法先獲得當(dāng)前線程,再調(diào)用線程對象的getName()方法來獲得線程名字

只能對處于新建狀態(tài)的線程調(diào)用start()方法,否則將引發(fā)IllegaIThreadStateExccption異常

調(diào)用線程對象的start()方法之后,該線程立即進(jìn)入就緒狀態(tài)——就緒狀態(tài)相當(dāng)于"等待執(zhí)行",但該線程并未真正進(jìn)入運(yùn)行狀態(tài)。如果希望調(diào)用子線程的start()方法后子線程立即開始執(zhí)行,程序可以使用Thread.slepp(1)來讓當(dāng)前運(yùn)行的線程(主線程)睡眠1毫秒,因?yàn)樵谶@1毫秒內(nèi)CPU不會空閑,它回去執(zhí)行另一個(gè)處于就緒狀態(tài)的線程,這樣就可以讓子線程立即開始執(zhí)行

運(yùn)行和阻塞狀態(tài)

當(dāng)發(fā)生如下情況時(shí),線程將會進(jìn)入阻塞狀態(tài)

線程調(diào)用sleep()方法主動放棄所占用的處理器資源

線程調(diào)用了一個(gè)阻塞式IO方法,在該方法返回之前,該線程被阻塞

線程試圖獲得一個(gè)同步監(jiān)視器,但該同步監(jiān)視器正被其他線程所持有

線程在等待某個(gè)通知(notify)

程序調(diào)用了線程的suspend()方法將該線程掛起。但這個(gè)方法容易導(dǎo)致死鎖,所以應(yīng)該盡量避免使用該方法

當(dāng)前正在執(zhí)行的線程被阻塞之后,其他線程就可以獲得執(zhí)行的機(jī)會。被阻塞的線程會在合適的時(shí)候重新進(jìn)入就緒狀態(tài),注意是就緒狀態(tài)而不是運(yùn)行狀態(tài)。也就是說,被阻塞線程的阻塞解除后,必須重新等待線程調(diào)度器再次調(diào)度它

當(dāng)發(fā)生如下特定情況時(shí)可以解除上述阻塞,該讓線程重新進(jìn)入就緒狀態(tài):

調(diào)用sleep()方法的線程經(jīng)過了指定時(shí)間

線程調(diào)用的阻塞式IO方法已經(jīng)返回

線程成功地獲得了試圖取得的同步監(jiān)視器

線程正在等待某個(gè)通知時(shí),其他線程發(fā)出了個(gè)通知

處于掛起狀態(tài)的線程被調(diào)甩了resdme()恢復(fù)方法

線程狀態(tài)轉(zhuǎn)換圖

線程從阻塞狀態(tài)只能進(jìn)入就緒狀態(tài),無法直接進(jìn)入運(yùn)行狀態(tài)。而就緒和運(yùn)行狀態(tài)之間的轉(zhuǎn)換通常不受程序控制,而是由系統(tǒng)線程調(diào)度所決定。當(dāng)處于就緒狀態(tài)的線程獲得處理器資源時(shí),該線程進(jìn)入運(yùn)行狀態(tài);當(dāng)處于運(yùn)行狀態(tài)的線程失去處理器資源時(shí),該線程進(jìn)入就緒狀態(tài)。但有一個(gè)方法例外,調(diào)用yield()方法可以讓運(yùn)行狀態(tài)的線程轉(zhuǎn)入就緒狀態(tài)

線程死亡

線程會以如下3種方式結(jié)束,結(jié)束后就處于死亡狀態(tài):

run()或call()方法執(zhí)行完成,線程正常結(jié)束

線程拋出一個(gè)未捕獲的Exception或Error

直接調(diào)用該線程stop()方法來結(jié)束該線程——該方法容易導(dǎo)致死鎖,通常不推薦使用

當(dāng)主線程結(jié)束時(shí),其他線程不受任何影響,并不會隨之結(jié)束。一旦子線程啟動起來后,它就擁有和主線程相同的地位,它不會受主線程的影響

為了測試某個(gè)線程是否已經(jīng)死亡,可以調(diào)用線程對象的isAlive()方法,當(dāng)線程處于就緒、運(yùn)行、阻塞三種狀態(tài)時(shí),該方法將返回true;當(dāng)線程處于新建、死亡狀態(tài)時(shí),該方法將返回false

不要試圖對一個(gè)已經(jīng)死亡的線程調(diào)用start()方法使它重新啟動,死亡就是死亡,該線程將不可再次作為線程執(zhí)行。如下程序嘗試對處于死亡狀態(tài)的線程再次調(diào)用start()方法,將引發(fā)IllegaIThreadStateException異常,這表明處于死亡狀態(tài)的線程無法再次運(yùn)行了

public class StartDead extends Thread
{
    private int i ;
    // 重寫run方法,run方法的方法體就是線程執(zhí)行體
    public void run()
    {
        for ( ; i < 100 ; i++ )
        {
            System.out.println(getName() +  " " + i);
        }
    }
    public static void main(String[] args)
    {
        // 創(chuàng)建線程對象
        StartDead sd = new StartDead();
        for (int i = 0; i < 300;  i++)
        {
            // 調(diào)用Thread的currentThread方法獲取當(dāng)前線程
            System.out.println(Thread.currentThread().getName() +  " " + i);
            if (i == 20)
            {
                // 啟動線程
                sd.start();
                // 判斷啟動后線程的isAlive()值,輸出true
                System.out.println(sd.isAlive());
    

    }
        // 只有當(dāng)線程處于新建、死亡兩種狀態(tài)時(shí)isAlive()方法返回false。
        // 當(dāng)i > 20,則該線程肯定已經(jīng)啟動過了,如果sd.isAlive()為假時(shí),
        // 那只能是死亡狀態(tài)了。
        if (i > 20 && !sd.isAlive())

        {
            // 試圖再次啟動該線程
            sd.start();
        }
    }
}

}

控制線程 join線程

Thread提供了讓一個(gè)線程等待另一個(gè)線程完成的方法——join()方法。當(dāng)在某個(gè)程序執(zhí)行流中調(diào)用其他線程的join()方法時(shí),調(diào)用線程將被阻塞,直到被join()方法加入的join線程執(zhí)行完為止

join()方法通常由使用線程的程序調(diào)用,以將大問題劃分成許多小問題,每個(gè)小問題分配一個(gè)線程。當(dāng)所有的小問題都得到處理后,再調(diào)用主線程來進(jìn)一步操作

public class JoinThread extends Thread
{
    // 提供一個(gè)有參數(shù)的構(gòu)造器,用于設(shè)置該線程的名字
    public JoinThread(String name)
    {
        super(name);
    }
    // 重寫run()方法,定義線程執(zhí)行體
    public void run()
    {
        for (int i = 0; i < 100 ; i++ )
        {
            System.out.println(getName() + "  " + i);
        }
    }
    public static void main(String[] args)throws Exception
    {
        // 啟動子線程
        new JoinThread("新線程").start();
        for (int i = 0; i < 100 ; i++ )
        {
            if (i == 20)
            {
                JoinThread jt = new JoinThread("被Join的線程");
                jt.start();
                // main線程調(diào)用了jt線程的join()方法
                // main線程必須等jt執(zhí)行結(jié)束才會向下執(zhí)行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()
                + "  " + i);
        }
    }
}


主方法開始時(shí)就啟動了名為“新線程”的子線程,該子線程將會和main線程并發(fā)執(zhí)行。當(dāng)主線程的循環(huán)變量i等于20時(shí),啟動了名為“被Join的線程”的線程,該線程不會和main線程并發(fā)執(zhí)行,main線程必須等該線程執(zhí)行結(jié)束后才可以向下執(zhí)行。在名為“被Join的線程”的線程執(zhí)行時(shí),實(shí)際上只有2個(gè)子線程并發(fā)執(zhí)行,而主線程處于等待狀態(tài)

join()方法有如下三種重載形式:

join():等待被join的線程執(zhí)行完成

join(long millis):等待被join的線程的時(shí)間最長為millis毫秒。如果在millis毫秒內(nèi)被join的線程還沒有執(zhí)行結(jié)束,則不再等待

join(long millis, int nanos):等待被join的線程的時(shí)間最長為millis毫秒加nanos毫微秒

后臺線程

后臺線程:在后臺運(yùn)行的,它的任務(wù)是為其他的線程提供服務(wù),這種線程被稱為“后臺線程(Daemon Thread)”,又稱為“守護(hù)線程”或“精靈線程”。JVM的垃圾回收線程就是典型的后臺線程

后臺線程有個(gè)特征:如果所有的前臺線程都死亡,后臺線程會自動死亡

調(diào)用Thread對象的setDaemon(true)方法可將指定線程設(shè)置成后臺線程

以下程序?qū)?zhí)行線程設(shè)置成后臺線程,可以看到當(dāng)所有的前臺線程死亡時(shí),后臺線程隨之死亡。當(dāng)整個(gè)虛擬機(jī)中只剩下后臺線程時(shí),程序就沒有繼續(xù)運(yùn)行的必要了,所以虛擬機(jī)也退出

public class DaemonThread extends Thread
{
    // 定義后臺線程的線程執(zhí)行體與普通線程沒有任何區(qū)別
    public void run()
    {
        for (int i = 0; i < 1000 ; i++ )
        {
            System.out.println(getName() + "  " + i);
        }
    }
    public static void main(String[] args)
    {
        DaemonThread t = new DaemonThread();
        // 將此線程設(shè)置成后臺線程
        t.setDaemon(true);
        // 啟動后臺線程
        t.start();
        for (int i = 0 ; i < 10 ; i++ )
        {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
        // -----程序執(zhí)行到此處,前臺線程(main線程)結(jié)束------
        // 后臺線程也應(yīng)該隨之結(jié)束
    }
}

先將t設(shè)置成后臺線程,然后啟動該線程,本來該線程應(yīng)該執(zhí)行到i等于999時(shí)才會結(jié)束,但運(yùn)行程序時(shí)不難發(fā)現(xiàn)該后臺線程無法運(yùn)行到999,因?yàn)橹骶€程(程序中唯一的前臺線程)運(yùn)行結(jié)束后,JVM主動退出,因此后臺線程也就結(jié)束

Thread類提供一個(gè)isDaemon()方法,用來判斷指定線程是否為后臺線程

前臺線程創(chuàng)建的子線程默認(rèn)就是前臺線程,后臺線程創(chuàng)建的子線程默認(rèn)是后臺線程

前臺線程死亡后,JVM會通知后臺線程死亡,但從它接收指令到做出響應(yīng),需要一定時(shí)間。而且要將某個(gè)線程設(shè)置為后臺線程,必須在該線程啟動之前設(shè)置,也就是說,setDaemon(true)必須在start()方法之前調(diào)用,否則會引發(fā)IllegalThreadStateException異常

線程睡眠:sleep

如果需要讓當(dāng)前正在執(zhí)行的線程暫停一段時(shí)間,并進(jìn)入阻塞狀態(tài),則可以通過調(diào)用Thread類的靜態(tài)sleep()方法來實(shí)現(xiàn)。sleep()方法有兩種重載形式

static void sleep(long millis):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒,并進(jìn)入阻塞狀態(tài),該方法受到系統(tǒng)計(jì)時(shí)器和線程調(diào)度器的精度與準(zhǔn)確度的影響

static void sleep(long millis, int nanos):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒加nanos毫微秒,并進(jìn)入阻塞狀態(tài),該方法受到系統(tǒng)計(jì)時(shí)器和線程調(diào)度器的精度與準(zhǔn)確度影響(不建議)

當(dāng)當(dāng)前線程調(diào)用sleep()方法進(jìn)入阻塞狀態(tài)后,在其睡眠時(shí)間內(nèi),該線程不會獲得執(zhí)行的機(jī)會,即使系統(tǒng)中沒有其他可執(zhí)行的線程,處于sleep()的線程也不會執(zhí)行,因此sleep()方法常用來暫停程序的執(zhí)行

import java.util.*;
public class SleepTest
{
    public static void main(String[] args) throws Exception
    {
        for (int i = 0; i < 10 ; i++ )
        {
            System.out.println("當(dāng)前時(shí)間: " + new Date());
            // 調(diào)用sleep方法讓當(dāng)前線程暫停1s。
            Thread.sleep(1000);
        }
    }
}
線程讓步:yield

它可以讓當(dāng)前正在執(zhí)行的線程暫停,但不會阻塞該線程,它只是將該線程轉(zhuǎn)入就緒狀態(tài)。yield()只是讓當(dāng)前線程暫停一下,讓系統(tǒng)的線程調(diào)度器重新調(diào)度一次,完全可能的情況是:當(dāng)某個(gè)線程調(diào)用了yield()方法暫停之后,線程調(diào)度器又將其調(diào)度出來重新執(zhí)行

實(shí)際上,當(dāng)某個(gè)線程調(diào)用yield()方法暫停之后,只要優(yōu)先級與當(dāng)前線程相同,或者優(yōu)先級比當(dāng)前線程更高的處于就緒狀態(tài)的線程才會獲得執(zhí)行的機(jī)會

public class YieldTest extends Thread
{
    public YieldTest(String name)
    {
        super(name);
    }
    // 定義run方法作為線程執(zhí)行體
    public void run()
    {
        for (int i = 0; i < 50 ; i++ )
        {
            System.out.println(getName() + "  " + i);
            // 當(dāng)i等于20時(shí),使用yield方法讓當(dāng)前線程讓步
            if (i == 20)
            {
                Thread.yield();
            }
        }
    }
    public static void main(String[] args)throws Exception
    {
        // 啟動兩條并發(fā)線程
        YieldTest yt1 = new YieldTest("高級");
        // 將ty1線程設(shè)置成最高優(yōu)先級
        yt1.setPriority(Thread.MAX_PRIORITY);
        yt1.start();
        YieldTest yt2 = new YieldTest("低級");
        // 將yt2線程設(shè)置成最低優(yōu)先級
        yt2.setPriority(Thread.MIN_PRIORITY);
        yt2.start();
    }
}

關(guān)于sleep()方法和yield()方法的區(qū)別如下:

sleep()方法暫停當(dāng)前線程后,會給其他線程執(zhí)行機(jī)會,不會理會其他線程的優(yōu)先級;但yield()方法只會給優(yōu)先級相同,或優(yōu)先級更高的線程執(zhí)行機(jī)會

sleep()方法會將線程轉(zhuǎn)入阻塞狀態(tài),直到經(jīng)過阻塞時(shí)間才會轉(zhuǎn)入就緒狀態(tài);而yield()方法不會將線程轉(zhuǎn)入阻塞狀態(tài),它只是強(qiáng)制當(dāng)前線程進(jìn)入就緒狀態(tài)。因此完全有可能某個(gè)線程調(diào)用yield()方法暫停之后,立即再次獲得處理器資源被執(zhí)行

sleep()方法聲明拋出了InterruptedException異常,所以調(diào)用sleep()方法時(shí)要么捕捉該異常,要么顯式聲明拋出該異常;而yield()方法則沒有聲明拋出任何異常

sleep()方法比yield()方法有更好的可移植性,通常不建議使用yield()方法來控制并發(fā)線程的執(zhí)行

改變線程優(yōu)先級

每個(gè)線程默認(rèn)的優(yōu)先級都與創(chuàng)建它的父線程的優(yōu)先級相同,在默認(rèn)情況下,main線程具有普通優(yōu)先級,由main線程創(chuàng)建的子線程也具有普通優(yōu)先級

Thread類提供了setPriority(int newPriority)、getPriority()方法來設(shè)置和返回指定線程的優(yōu)先級,其中setPriority()方法的參數(shù)可以是一個(gè)整數(shù),范圍是1~10之間,也可以使用Thread類的如下三個(gè)靜態(tài)常量:

MAX_PRIORITY:其值是10

MIN_PRIORITY:其值是1

NORM_PRIORITY:其值是5

public class PriorityTest extends Thread
{
    // 定義一個(gè)有參數(shù)的構(gòu)造器,用于創(chuàng)建線程時(shí)指定name
    public PriorityTest(String name)
    {
        super(name);
    }
    public void run()
    {
        for (int i = 0 ; i < 50 ; i++ )
        {
            System.out.println(getName() +  ",其優(yōu)先級是:" + getPriority() + ",循環(huán)變量的值為:" + i);
        }
    }
    public static void main(String[] args)
    {
        // 改變主線程的優(yōu)先級
        Thread.currentThread().setPriority(6);
        for (int i = 0 ; i < 30 ; i++ )
        {
            if (i == 10)
            {
                PriorityTest low  = new PriorityTest("低級");
                low.start();
                System.out.println("創(chuàng)建之初的優(yōu)先級:" + low.getPriority());
                // 設(shè)置該線程為最低優(yōu)先級
                low.setPriority(Thread.MIN_PRIORITY);
            }
            if (i == 20)
            {
                PriorityTest high = new PriorityTest("高級");
                high.start();
                System.out.println("創(chuàng)建之初的優(yōu)先級:" + high.getPriority());
                // 設(shè)置該線程為最高優(yōu)先級
                high.setPriority(Thread.MAX_PRIORITY);
            }
        }
    }
}
線程同步 同步代碼塊

Java的多線程支持引入了同步監(jiān)視器來解決這個(gè)問題,使用同步監(jiān)視器的通用方法就是同步代碼塊。同步代碼塊的語法格式如下:

synchronized(obj)
{
    ...
    //此處的代碼就是同步代碼塊
}

其中,obj是一個(gè)同步監(jiān)視器,上面代碼的含義是:線程開始執(zhí)行同步代碼塊之前,必須先獲得對同步監(jiān)視器的鎖定

任何時(shí)刻只能有一個(gè)線程可以獲得對同步監(jiān)視器的鎖定,當(dāng)同步代碼執(zhí)行完成后,該線程會釋放對該同步監(jiān)視器的鎖定

同步監(jiān)視器的目的:阻止兩個(gè)線程對同一個(gè)共享資源進(jìn)行并發(fā)訪問,因此通常推薦使用可能被并發(fā)訪問的共享資源充當(dāng)同步監(jiān)視器

public class DrawThread extends Thread
{
    // 模擬用戶賬戶
    private Account account;
    // 當(dāng)前取錢線程所希望取的錢數(shù)
    private double drawAmount;
    public DrawThread(String name, Account account, double drawAmount)
    {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    // 當(dāng)多條線程修改同一個(gè)共享數(shù)據(jù)時(shí),將涉及數(shù)據(jù)安全問題。
    public void run()
    {
        // 使用account作為同步監(jiān)視器,任何線程進(jìn)入下面同步代碼塊之前
        // 必須先獲得對account賬戶的鎖定——其他線程無法獲得鎖,也就無法修改它
        // 這種做法符合:“加鎖→修改→釋放鎖”的邏輯
        synchronized (account) 
        {
            // 賬戶余額大于取錢數(shù)目
            if (account.getBalance() >= drawAmount)
            {
                // 吐出鈔票
                System.out.println(getName() + "取錢成功!吐出鈔票:" + drawAmount);
                try
                {
                    Thread.sleep(1);
                }
                catch (InterruptedException ex)
                {
                    ex.printStackTrace();
                }
                // 修改余額
                account.setBalance(account.getBalance() - drawAmount);
                System.out.println("	余額為: " + account.getBalance());
            }
            else
            {
                System.out.println(getName() + "取錢失敗!余額不足!");
            }
        }
        // 同步代碼塊結(jié)束,該線程釋放同步鎖
    }
}

使用synchronize將run()方法里的方法體修改成同步代碼塊,該同步代碼塊的同步監(jiān)視器是account對象,該做法符合“加鎖→修改→釋放鎖”的邏輯,任何線程在修改指定資源之前,首先對該資源加鎖,在加鎖期間其他線程無法修改該資源,當(dāng)該線程修改完成后,該線程釋放對該資源的鎖定。通過這種方式就可以保證并發(fā)線程在任何一個(gè)時(shí)刻只有一條線程可以進(jìn)入修改共享資源的代碼區(qū)(也稱為臨界區(qū)),所以同一個(gè)時(shí)刻最多只有一條線程處于臨界區(qū)內(nèi),從而保證了線程的安全

同步方法

同步方法:使用synchronized關(guān)鍵字來修飾的某個(gè)方法。對于同步方法而言,無需顯式指定同步監(jiān)視器,同步方法的同步監(jiān)視器是this,也就是該對象本身

通過使用同步方法可以非常方便地將某類變成線程安全的類,線程安全的類具有如下特性:

該類的對象可以被多個(gè)線程安全的訪問

每個(gè)線程調(diào)用該對象的任何方法之后都將得到正確的結(jié)果

每個(gè)線程調(diào)用該對象的任何方法之后,該對象狀態(tài)依然保持合理狀態(tài)

public class Account
{
    // 封裝賬戶編號、賬戶余額兩個(gè)成員變量
    private String accountNo;
    private double balance;
    public Account(){}
    // 構(gòu)造器
    public Account(String accountNo , double balance)
    {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    // accountNo的setter和getter方法
    public void setAccountNo(String accountNo)
    {
        this.accountNo = accountNo;
    }
    public String getAccountNo()
    {
        return this.accountNo;
    }
    // 因此賬戶余額不允許隨便修改,所以只為balance提供getter方法,
    public double getBalance()
    {
        return this.balance;
    }

    // 提供一個(gè)線程安全draw()方法來完成取錢操作
    public synchronized void draw(double drawAmount)
    {
        // 賬戶余額大于取錢數(shù)目
        if (balance >= drawAmount)
        {
            // 吐出鈔票
            System.out.println(Thread.currentThread().getName()
                + "取錢成功!吐出鈔票:" + drawAmount);
            try
            {
                Thread.sleep(1);
            }
            catch (InterruptedException ex)
            {
                ex.printStackTrace();
            }
            // 修改余額
            balance -= drawAmount;
            System.out.println("	余額為: " + balance);
        }
        else
        {
            System.out.println(Thread.currentThread().getName()
                + "取錢失敗!余額不足!");
        }
    }

    // 下面兩個(gè)方法根據(jù)accountNo來重寫hashCode()和equals()方法
    public int hashCode()
    {
        return accountNo.hashCode();
    }
    public boolean equals(Object obj)
    {
        if(this == obj)
            return true;
        if (obj !=null
            && obj.getClass() == Account.class)
        {
            Account target = (Account)obj;
            return target.getAccountNo().equals(accountNo);
        }
        return false;
    }
}

以上程序中增加了一個(gè)代表取錢操作的draw方法,并使用了synchronized關(guān)鍵字修飾了該方法,把該方法變成同步方法。同步方法的同步監(jiān)視器是this,因此對于同一個(gè)Account賬戶來說,任何時(shí)刻只能有一條線程獲得對Account對象的鎖定,然后進(jìn)入draw方法執(zhí)行取錢操作——這樣也可以保證多條線程并發(fā)存錢的線程安全

synchronized關(guān)鍵字可以修飾方法,可以修飾代碼塊,但不能修飾構(gòu)造方法、成員變量等

在面向?qū)ο罄镉幸环N流行的設(shè)計(jì)方式:Domain Driven Design(即領(lǐng)域驅(qū)動設(shè)計(jì),簡稱DDO),這種方式認(rèn)為每個(gè)類都應(yīng)該是完備的領(lǐng)域?qū)ο螅鏏ccount它代表用戶賬戶,它應(yīng)該提供賬戶的相關(guān)方法,例如通過draw()方法來執(zhí)行取錢操作,而不是直接將setBalance()方法暴露出來任人操作,這樣才可以更好地保證Account對象的完整性和一致性。可變類的線程安全是以降低程序的運(yùn)行效率作為代價(jià)的,為了減少線程安全所帶來的負(fù)面影響,程序可以采取如下策略:

不要對線程安全類的所有方法都進(jìn)行同步,只對那些會改變競爭資源(競爭資源也就是共享資源)的方法進(jìn)行同步,例如上面的Account類中accountNo屬性就無需同步,所以程序只對draw方法進(jìn)行同步控制、

如果可變類有兩種運(yùn)行環(huán)境:單線程環(huán)境和多線程環(huán)境,則應(yīng)該為該可變類提供兩種版本:線程不安全版本和線程安全版本。在單線程環(huán)境中使用線程不安全版本以保證性能,在多線程環(huán)境中使用線程安全版本

釋放同步監(jiān)視器的鎖定

任何線程進(jìn)入同步代碼塊、同步方法之前,必須先獲得對同步監(jiān)視器的鎖定,那么何時(shí)會釋放對同步監(jiān)視器的鎖定呢?程序無法顯式釋放對同步監(jiān)視器的鎖定,線程會在如下幾種情況下釋放對同步監(jiān)視器的鎖定:

當(dāng)前線程的同步方法、同步代碼塊執(zhí)行結(jié)束,當(dāng)前線程即釋放同步監(jiān)視器

當(dāng)線程在同步代碼塊、同步方法中遇到break、return終止了該代碼塊、該方法的繼續(xù)執(zhí)行,當(dāng)前線程將會釋放同步監(jiān)視器

當(dāng)線程在同步代碼塊、同步方法中出現(xiàn)了未處理的Error或Exception,導(dǎo)致了該代碼塊、該方法異常結(jié)束時(shí),當(dāng)前線程將會釋放同步監(jiān)視器

當(dāng)線程執(zhí)行同步代碼塊或同步方法時(shí),程序執(zhí)行了同步監(jiān)視器對象的wait()方法,則當(dāng)前線程暫停,并釋放同步監(jiān)視器

在下面的情況下,線程不會釋放同步監(jiān)視器:

線程執(zhí)行同步代碼塊或同步方法時(shí),程序調(diào)用Thread.sleep()、Thread.yield()方法來暫停當(dāng)前線程的執(zhí)行,當(dāng)前線程不會釋放同步監(jiān)視器

線程執(zhí)行同步代碼塊時(shí),其他線程調(diào)用了該線程的suspend()方法將該線程掛起,該線程不會釋放同步監(jiān)視器。當(dāng)然,應(yīng)該盡量避免使用suspend()和resume()方法來控制線程

同步鎖(Lock)

Java5提供了功能更強(qiáng)大的線程同步機(jī)制——通過顯式定義同步鎖對象來實(shí)現(xiàn)同步,在這種機(jī)制下,同步鎖由Lock對象充當(dāng)

Lock是控制多個(gè)線程對共享資源進(jìn)行訪問的工具。鎖提供了對共享資源的獨(dú)占訪問,每次只能有一個(gè)線程對Lock對象加鎖,線程開始訪問共享資源之前應(yīng)該首先獲Lock對象

某些鎖可能允許對共享資源并發(fā)訪問,如ReadWriteLock(讀寫鎖),Lock、ReadWriteLock是Java5提供的兩個(gè)根接口,并為Lock提供了ReentrantLock(可重入鎖)實(shí)現(xiàn)類,為ReadWriteLock提供了ReentrantReadWriteLock實(shí)現(xiàn)類

Java8之后新增了新型的StampedLock。它在大多數(shù)場景中可以替代傳統(tǒng)的ReentrantReadWriteLock。為讀寫操作提供三種鎖模式:Writing、ReadingOptimistic、Reading

在實(shí)現(xiàn)線程安全的控制中,比較常用的是ReadWriteLock(可重入鎖)。使用該Lock對象可以顯式地加鎖、釋放鎖,示例如下:

class LockTest
{
    // 定義鎖對象
    private final ReadWriteLock lock = new ReadWriteLock();
    // ...
    // 定義需要保證線程安全的方法
    public void m()
    {
        // 加鎖
        lock.lock();
        try
        {
            // 需要保證線程安全的代碼
            // ... method body
        }
        // 使用finally塊來保證釋放鎖
        finally
        {
            lock.unlock();
        }
    }
}

通常建議使用finally塊來確保在必要時(shí)釋放鎖

使用Lock時(shí)顯式使用Lock對象作為同步鎖,而使用同步方法時(shí)系統(tǒng)隱式使用當(dāng)前對象作為同步監(jiān)視器,都符合“加鎖——修改——釋放鎖”的操作模式

同步方法和同步代碼塊使用與競爭資源相關(guān)的,隱式的同步監(jiān)視器,并且強(qiáng)制要求加鎖和釋放鎖要出現(xiàn)在同一個(gè)塊結(jié)構(gòu)中,而且當(dāng)獲取了多個(gè)鎖時(shí),它們必須以相反的順序釋放,且必須在所有鎖被獲取時(shí)相同的范圍內(nèi)釋放鎖

Lock提供了同步方法和同步代碼塊沒有的功能,包括用于非塊結(jié)構(gòu)的tryLock方法,以及試圖獲取可中斷鎖的lockInterruptibly()方法,還有獲取超時(shí)失效所的tryLock(long, TimeUnit)方法

ReentrantLock鎖具有重入性,也就是線程可以對已經(jīng)加鎖的ReentrantLock鎖再次加鎖,ReentrantLock會維持一個(gè)計(jì)數(shù)器來跟蹤Lock方法的嵌套調(diào)用,線程在每次lock()加鎖后,必須顯式調(diào)用unLock()來釋放鎖,所一段被鎖保護(hù)的代碼可以調(diào)用另一個(gè)被相同鎖保護(hù)的方法

死鎖

當(dāng)兩個(gè)線程相互等待對方釋放同步監(jiān)視器時(shí)就會發(fā)生死鎖。一旦出現(xiàn)死鎖,整個(gè)程序既不會發(fā)生任何異常,也不會給出任何提示,只是所有線程處于阻塞狀態(tài),無法繼續(xù)

class A
{
    public synchronized void foo( B b )
    {
        System.out.println("當(dāng)前線程名: " + Thread.currentThread().getName() + " 進(jìn)入了A實(shí)例的foo()方法" );     // ①
        try
        {
            Thread.sleep(200);
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
        System.out.println("當(dāng)前線程名: " + Thread.currentThread().getName() + " 企圖調(diào)用B實(shí)例的last()方法");    // ③
        b.last();
    }
    public synchronized void last()
    {
        System.out.println("進(jìn)入了A類的last()方法內(nèi)部");
    }
}
class B
{
    public synchronized void bar( A a )
    {
        System.out.println("當(dāng)前線程名: " + Thread.currentThread().getName() + " 進(jìn)入了B實(shí)例的bar()方法" );   // ②
        try
        {
            Thread.sleep(200);
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
        System.out.println("當(dāng)前線程名: " + Thread.currentThread().getName() + " 企圖調(diào)用A實(shí)例的last()方法");  // ④
        a.last();
    }
    public synchronized void last()
    {
        System.out.println("進(jìn)入了B類的last()方法內(nèi)部");
    }
}
public class DeadLock implements Runnable
{
    A a = new A();
    B b = new B();
    public void init()
    {
        Thread.currentThread().setName("主線程");
        // 調(diào)用a對象的foo方法
        a.foo(b);
        System.out.println("進(jìn)入了主線程之后");
    }
    public void run()
    {
        Thread.currentThread().setName("副線程");
        // 調(diào)用b對象的bar方法
        b.bar(a);
        System.out.println("進(jìn)入了副線程之后");
    }
    public static void main(String[] args)
    {
        DeadLock dl = new DeadLock();
        // 以dl為target啟動新線程
        new Thread(dl).start();
        // 調(diào)用init()方法
        dl.init();
    }
}

由圖可知,程序既無法向下執(zhí)行,也不會拋出任何異常,就一直“僵著”。因?yàn)锳、B對象的方法都是同步方法,A、B對象都是同步鎖。程序中兩個(gè)線程執(zhí)行,一個(gè)線程的線程執(zhí)行體是DeadLock類的run()方法,另一個(gè)線程的線程執(zhí)行體是DeadLock的init()方法(主線程調(diào)用了init()方法)。其中run()方法讓B對象調(diào)用bar()方法,而init()方法讓A對象調(diào)用foo()方法

如圖所示,init()方法先執(zhí)行,調(diào)用了A對象的foo()方法,進(jìn)入foo()方法之前,該線程對A對象加鎖——當(dāng)程序執(zhí)行到①代碼時(shí),主線程暫停200ms

CPU切換到執(zhí)行另一個(gè)線程,讓B對象執(zhí)行bar()方法,所以看到副線程開始執(zhí)行B實(shí)例的bar()方法,進(jìn)入bar()方法之前,對B對象加鎖——當(dāng)程序執(zhí)行到②代碼時(shí),副線程也暫停200ms

接下來主線程會先醒過來,繼續(xù)向下執(zhí)行,直到③代碼處希望調(diào)用B對象的last()方法——執(zhí)行該方法之前必須先對B對象加鎖,但此時(shí)副線程正保持著B對象的鎖,所以主線程阻塞

接下來副線程應(yīng)該醒過來了,繼續(xù)向下執(zhí)行,直到④代碼處希望調(diào)用A對象的last()方法——————執(zhí)行此方法之前必須先對A對象加鎖,但此時(shí)主線程沒有釋放對A對象的鎖——至此,就出現(xiàn)了主線程保持著A對象的鎖,等待B對象加鎖,而副線程保持著B對象的鎖,等待著A對象的鎖,等待對A對象加鎖,兩個(gè)線程互相等待對方先釋放,所以就出現(xiàn)了死鎖

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/66691.html

相關(guān)文章

  • Java線程學(xué)習(xí)(一)Java線程入門

    摘要:最近聽很多面試的小伙伴說,網(wǎng)上往往是一篇一篇的多線程的文章,除了書籍沒有什么學(xué)習(xí)多線程的一系列文章。將此線程標(biāo)記為線程或用戶線程。 最近聽很多面試的小伙伴說,網(wǎng)上往往是一篇一篇的Java多線程的文章,除了書籍沒有什么學(xué)習(xí)多線程的一系列文章。但是僅僅憑借一兩篇文章很難對多線程有系統(tǒng)的學(xué)習(xí),而且面試的時(shí)候多線程這方面的知識往往也是考察的重點(diǎn),所以考慮之下決定寫一系列關(guān)于Java多線程的文章...

    Donne 評論0 收藏0
  • Java線程專題一:并發(fā)所面臨的問題

    摘要:但是并不是什么多線程就可以隨便用,有的時(shí)候多線程反而會造成系統(tǒng)的負(fù)擔(dān),而且多線程還會造成其他的數(shù)據(jù)問題,下面就來介紹一下多線程面臨的問題。下面這張圖是多線程運(yùn)行時(shí)候的情況,我們發(fā)現(xiàn)上下文切換的次數(shù)暴增。 并發(fā)的概念: 在Java中是支持多線程的,多線程在有的時(shí)候可以大提高程序的速度,比如你的程序中有兩個(gè)完全不同的功能操作,你可以讓兩個(gè)不同的線程去各自執(zhí)行這兩個(gè)操作,互不影響,不需要執(zhí)行...

    madthumb 評論0 收藏0
  • Java線程可以分組,還能這樣玩!

    摘要:如圖所示,帶有的所有線程構(gòu)造方法都可以定義線程組的。線程組還能統(tǒng)一設(shè)置組內(nèi)所有線程的最高優(yōu)先級,線程單獨(dú)設(shè)置的優(yōu)先級不會高于線程組設(shè)置的最大優(yōu)先級。 前面的文章,棧長和大家分享過多線程創(chuàng)建的3種方式《實(shí)現(xiàn) Java 多線程的 3 種方式》。 但如果線程很多的情況下,你知道如何對它們進(jìn)行分組嗎? 和 Dubbo 的服務(wù)分組一樣,Java 可以對相同性質(zhì)的線程進(jìn)行分組。 來看下線程類 Th...

    biaoxiaoduan 評論0 收藏0
  • JAVA 線程和并發(fā)基礎(chǔ)

    摘要:線程可以被稱為輕量級進(jìn)程。一個(gè)守護(hù)線程是在后臺執(zhí)行并且不會阻止終止的線程。其他的線程狀態(tài)還有,和。上下文切換是多任務(wù)操作系統(tǒng)和多線程環(huán)境的基本特征。在的線程中并沒有可供任何對象使用的鎖和同步器。 原文:Java Multi-Threading and Concurrency Interview Questions with Answers 翻譯:并發(fā)編程網(wǎng) - 鄭旭東 校對:方騰飛 多...

    vboy1010 評論0 收藏0
  • Java線程學(xué)習(xí)(七)并發(fā)編程中一些問題

    摘要:相比與其他操作系統(tǒng)包括其他類系統(tǒng)有很多的優(yōu)點(diǎn),其中有一項(xiàng)就是,其上下文切換和模式切換的時(shí)間消耗非常少。因?yàn)槎嗑€程競爭鎖時(shí)會引起上下文切換。減少線程的使用。很多編程語言中都有協(xié)程。所以如何避免死鎖的產(chǎn)生,在我們使用并發(fā)編程時(shí)至關(guān)重要。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)syn...

    dingding199389 評論0 收藏0
  • 學(xué)習(xí)Java線程的一些總結(jié)

    摘要:多線程環(huán)境下的一些問題安全性問題在沒有正確同步的情況下,多線程環(huán)境下程序可能得出錯(cuò)誤的結(jié)果。一些相關(guān)概念競爭條件多線程的環(huán)境下,程序執(zhí)行的結(jié)果取決于線程交替執(zhí)行的方式。而線程的交替操作順序是不可預(yù)測的,如此程序執(zhí)行的結(jié)果也是不可預(yù)測的。 入口 Java多線程的應(yīng)用復(fù)雜性之如jvm有限的幾個(gè)內(nèi)存方面的操作和規(guī)范,就像無數(shù)紛繁復(fù)雜的應(yīng)用邏輯建立在有限的指令集上。 如何寫出線程安全的程序,有...

    coolpail 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<