java多線程學(xué)習(xí)筆記

進(jìn)程:正在執(zhí)行的程序建炫,是一個(gè)動(dòng)態(tài)的過(guò)程

線程:是進(jìn)程中用于控制程序執(zhí)行的控制單元(執(zhí)行路徑,執(zhí)行情景)

進(jìn)程中至少有一個(gè)線程末荐。

在Java VM(java虛擬機(jī))啟動(dòng)的時(shí)候會(huì)有一個(gè)進(jìn)程java.exe.

該進(jìn)程中至少一個(gè)線程負(fù)責(zé)java程序的執(zhí)行墩新。
而且這個(gè)線程運(yùn)行的代碼存在于main方法中狸涌。
該線程稱之為主線程驯用。

擴(kuò)展:其實(shí)更細(xì)節(jié)說(shuō)明jvm挖腰,jvm啟動(dòng)不止一個(gè)線程街夭,還有負(fù)責(zé)垃圾回收機(jī)制的線程砰碴。

創(chuàng)建線程

創(chuàng)建線程的第一種方式:繼承Thread類

步驟:

1,定義類繼承Thread板丽。
2呈枉,復(fù)寫(xiě)Thread類中的run方法。
    目的:將自定義代碼存儲(chǔ)在run方法埃碱。讓線程運(yùn)行猖辫。

3,調(diào)用線程的start方法砚殿,
    該方法兩個(gè)作用:?jiǎn)?dòng)線程啃憎,調(diào)用run方法。

發(fā)現(xiàn)運(yùn)行結(jié)果每一次都不同似炎。因?yàn)槎鄠€(gè)線程都獲取cpu的執(zhí)行權(quán)辛萍。cpu執(zhí)行到誰(shuí)悯姊,誰(shuí)就運(yùn)行。
明確一點(diǎn)贩毕,在某一個(gè)時(shí)刻悯许,只能有一個(gè)程序在運(yùn)行(多核除外)。cpu在做著快速的切換辉阶,以達(dá)到看上去是同時(shí)運(yùn)行的效果先壕。

我們可以形象把多線程的運(yùn)行形容為在互相搶奪cpu的執(zhí)行權(quán)。

這就是多線程的一個(gè)特性:隨機(jī)性谆甜。誰(shuí)搶到誰(shuí)執(zhí)行启上,至于執(zhí)行多長(zhǎng),cpu說(shuō)的算店印。

為什么要覆蓋run方法呢冈在?

Thread類用于描述線程。該類就定義了一個(gè)功能按摘,用于存儲(chǔ)其他線程(非主線程)要運(yùn)行的代碼包券。該存儲(chǔ)功能就是run方法。

也就是說(shuō)Thread類中的run方法炫贤,用于存儲(chǔ)線程要運(yùn)行的代碼溅固。

創(chuàng)建線程的第二種方式:實(shí)現(xiàn)Runable接口

步驟:

1,定義類實(shí)現(xiàn)Runnable接口
2兰珍,覆蓋Runnable接口中的run方法侍郭。
    將線程要運(yùn)行的代碼存放在該run方法中。

3掠河,通過(guò)Thread類建立線程對(duì)象亮元。
4,將Runnable接口的子類對(duì)象作為實(shí)際參數(shù)傳遞給Thread類的構(gòu)造函數(shù)唠摹。
    為什么要將Runnable接口的子類對(duì)象傳遞給Thread的構(gòu)造函數(shù)爆捞。
    因?yàn)椋远x的run方法所屬的對(duì)象是Runnable接口的子類對(duì)象勾拉。
    所以要讓線程去指定指定對(duì)象的run方法煮甥。就必須明確該run方法所屬對(duì)象。


5藕赞,調(diào)用Thread類的start方法開(kāi)啟線程并調(diào)用Runnable接口子類的run方法成肘。

兩種創(chuàng)建現(xiàn)場(chǎng)的區(qū)別

實(shí)現(xiàn)方式和繼承方式有什么區(qū)別呢?

實(shí)現(xiàn)方式好處:避免了單繼承的局限性斧蜕。
在定義線程時(shí)双霍,建立使用實(shí)現(xiàn)方式。

兩種方式區(qū)別:

繼承Thread:線程代碼存放Thread子類run方法中。

實(shí)現(xiàn)Runnable店煞,線程代碼存在接口的子類的run方法蟹演。

線程中的方法

線程都有自己默認(rèn)的名稱:Thread-編號(hào) 該編號(hào)從0開(kāi)始。

線程中的常用方法

static Thread currentThread():獲取當(dāng)前線程對(duì)象顷蟀。
getName(): 獲取線程名稱酒请。

設(shè)置線程名稱:setName或者構(gòu)造函數(shù)。

多線程中出現(xiàn)的問(wèn)題

多線程的運(yùn)行出現(xiàn)了安全問(wèn)題鸣个。

問(wèn)題的原因:

當(dāng)多條語(yǔ)句在操作同一個(gè)線程共享數(shù)據(jù)時(shí)羞反,一個(gè)線程對(duì)多條語(yǔ)句只執(zhí)行了一部分,還沒(méi)有執(zhí)行完囤萤,
另一個(gè)線程參與進(jìn)來(lái)執(zhí)行昼窗。導(dǎo)致共享數(shù)據(jù)的錯(cuò)誤。

解決辦法:

對(duì)多條操作共享數(shù)據(jù)的語(yǔ)句涛舍,只能讓一個(gè)線程都執(zhí)行完澄惊。在執(zhí)行過(guò)程中,其他線程不可以參與執(zhí)行富雅。

Java對(duì)于多線程的安全問(wèn)題提供了專業(yè)的解決方式掸驱。

就是同步代碼塊,方法如下

synchronized(對(duì)象)//任意對(duì)象都行
{
    需要被同步的代碼

}

對(duì)象如同鎖没佑。持有鎖的線程可以在同步中執(zhí)行毕贼。
沒(méi)有持有鎖的線程即使獲取cpu的執(zhí)行權(quán),也進(jìn)不去蛤奢,因?yàn)闆](méi)有獲取鎖鬼癣。

同步的前提:

1,必須要有兩個(gè)或者兩個(gè)以上的線程啤贩。
2待秃,必須是多個(gè)線程使用同一個(gè)鎖。

必須保證同步中只能有一個(gè)線程在運(yùn)行瓜晤。

好處:解決了多線程的安全問(wèn)題锥余。

弊端:多個(gè)線程都需要判斷鎖,較為消耗資源痢掠,

示例代碼如下:

public class TicketDemo2 {

    public static void main(String[] args) {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }

}

class Ticket implements Runnable {
    private int tick = 100;
    Object obj = new Object();// 為synchronized提供對(duì)象

    public void run() {
        while (true) {
            synchronized (obj) {
                if (tick > 0) {
                     try{Thread.sleep(50);}catch(Exception e){}
                    System.out.println(Thread.currentThread().getName() + "....sale : " + tick--);
                }
            }
        }
    }
}

此段代碼的是模擬賣票,假設(shè)有100張票嘲恍,然后有4個(gè)線程同時(shí)賣票足画。
可以在Ticket類中有synchronized同步代碼塊,當(dāng)有同步代碼塊時(shí)佃牛,持有synchronized同步鎖的線程可以在同步中執(zhí)行淹辞。沒(méi)有持有鎖的線程即使獲取cpu的執(zhí)行權(quán),也進(jìn)不去俘侠,因?yàn)闆](méi)有獲取synchronized同步鎖象缀。線程將依序執(zhí)行蔬将,也就是有一個(gè)線程在進(jìn)入時(shí),另一個(gè)線程無(wú)法進(jìn)入央星。

如果沒(méi)有synchronized同步鎖霞怀,那么有可能會(huì)有以下問(wèn)題產(chǎn)生,當(dāng)tick剩下1張票的時(shí)候莉给,t1現(xiàn)場(chǎng)進(jìn)入了毙石,然后遇到Thread.sleep(50),失去執(zhí)行權(quán)颓遏。

接著t2線程執(zhí)行了了徐矩,此時(shí)tick仍然是1張票,因此t2也進(jìn)入if語(yǔ)句叁幢,也遇到sleep()方法滤灯,失去了執(zhí)行權(quán)。

然后t3線程也來(lái)了,遇到了與t1/t2一樣的狀況。

t4線程也既有可能遇到這種情況纵装。

最后t1開(kāi)始執(zhí)行了夺艰,tick為0了。但是此時(shí)t2也在if語(yǔ)句中上荡,也執(zhí)行語(yǔ)句,tick就為-1,然后是t3/t4拂募,導(dǎo)致了代碼運(yùn)行的結(jié)果出現(xiàn)了負(fù)數(shù),與設(shè)想中的不同窟她!

上面所描述的問(wèn)題就是多線程中所要解決的問(wèn)題陈症,這也是synchronized同步鎖的使用情景

同步鎖

synchronized可以給代碼塊上鎖,也可以給方法上鎖震糖,如下:

        // 同步代碼塊
        synchronized (對(duì)象) {
            ····· 代碼塊
        }
        
        // 同步方法
        public synchronized void show (){}

那么問(wèn)題來(lái)了录肯,在同步代碼塊中我們可以很清楚的看到synchronized給哪一個(gè)對(duì)象上鎖,那么同步方法又是給誰(shuí)上鎖呢吊说?

同步函數(shù)用的是哪一個(gè)鎖呢论咏?
函數(shù)需要被對(duì)象調(diào)用。那么函數(shù)都有一個(gè)所屬對(duì)象引用颁井,就是this厅贪,所以同步函數(shù)使用的鎖是this。

通過(guò)該程序進(jìn)行驗(yàn)證雅宾。

使用兩個(gè)線程來(lái)買票养涮。一個(gè)線程在同步代碼塊中。一個(gè)線程在同步函數(shù)中。都在執(zhí)行買票動(dòng)作贯吓。

代碼如下:

public class TicketDemo2 {

    public static void main(String[] args) {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t1.start();
        // 讓主線程休眠懈凹,將執(zhí)行權(quán)移交給t1/t2線程
        try{Thread.sleep(10);}catch(Exception e){}
        t.flag = false;
        t2.start();

    }

}

class Ticket implements Runnable {
    private int tick = 100;
    Object obj = new Object();
    // 使用flag
    boolean flag = true;

    public void run() {
        if (flag) {
            while (true) {
                // 使用的鎖是obj
                synchronized (obj) {
                    if (tick > 0) {
                        try{Thread.sleep(10);}catch(Exception e){}
                        System.out.println(Thread.currentThread().getName() + "....code : " + tick--);
                    }
                }
            }
        } else
            while (true)
                show();
    }
    // 同步方法的鎖對(duì)象是this,即所屬對(duì)象的引用
    public synchronized void show()    {  
        if (tick > 0) {
            try{Thread.sleep(10);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() + "....show.... : " + tick--);
        }
    }
}

上述代碼中添加了同步方法悄谐,并且設(shè)置了flag布爾值介评,用以讓線程能在中途從同步代碼塊中切換到同步方法中。

然后再控制臺(tái)中看到結(jié)果:

Thread-1....show.... : 4
Thread-0....code : 3
Thread-1....show.... : 2
Thread-1....show.... : 1
Thread-0....code : 0

這里居然賣出了0張票尊沸,這明顯是一個(gè)錯(cuò)誤的結(jié)果威沫,這是為什么呢?

答案在于我們雖然在多線程中使用了同步鎖洼专,但是我們的鎖對(duì)象并不是同一個(gè)對(duì)象棒掠,因?yàn)樵谕酱a塊中使用的是obj對(duì)象,如下:

synchronized (obj) {
·····代碼省略
}

但是當(dāng)obj替換成this的時(shí)候屁商,我們就能輸出正確的結(jié)果烟很,不在輸出0張票。

這足以證明在同步方法中使用的鎖對(duì)象就是所屬對(duì)象引用蜡镶。

靜態(tài)同步方法

static實(shí)際上也能使用同步鎖雾袱,我們將上述代碼修改,首先將同步方法修改為靜態(tài)同步方法官还,然后將tick也標(biāo)識(shí)為static芹橡,代碼如下:

private static  int tick = 100;
···

public static synchronized void show(){
····
}

調(diào)用方法跟前面一樣。

這時(shí)再次輸出了tick=0的錯(cuò)誤結(jié)果望伦!

這又是為什么呢林说?

這是因?yàn)殪o態(tài)方法中的同步鎖對(duì)象使用的不是this,因?yàn)殪o態(tài)方法中也不可以定義this屯伞。

其原因在于靜態(tài)進(jìn)內(nèi)存時(shí)腿箩,內(nèi)存中沒(méi)有本類對(duì)象,但是一定有該類對(duì)應(yīng)的字節(jié)碼文件對(duì)象劣摇。
即 類名.class 該對(duì)象的類型是Class

靜態(tài)的同步方法珠移,使用的鎖是該方法所在類的字節(jié)碼文件對(duì)象。 類名.class末融。

所以我們將同步代碼塊中的所對(duì)象修改為T(mén)icket.class時(shí)就能輸出正確結(jié)果钧惧,此時(shí)Ticket完整代碼如下:

class Ticket implements Runnable {
    private static  int tick = 100;
    Object obj = new Object();
    boolean flag = true;

    public void run() {
        if (flag) {
            while (true) {
                synchronized (Ticket.class) {
                    if (tick > 0) {
                        try{Thread.sleep(50);}catch(Exception e){}
                        System.out.println(Thread.currentThread().getName() + "....code : " + tick--);
                    }
                }
            }
        } else
            while (true)
                show();
    }
    // 靜態(tài)方法的所對(duì)象是    類名.class
    public static synchronized void show()    {  
        if (tick > 0) {
            try{Thread.sleep(50);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() + "....show.... : " + tick--);
        }
    }
}

單例模式中的懶漢式

單例模式中的懶漢式寫(xiě)法其實(shí)頗為復(fù)雜,因?yàn)橐紤]到多線程的問(wèn)題勾习。如果不添加synchronized進(jìn)行同步垢乙,在多線程的情況下,仍有可能導(dǎo)致創(chuàng)建了多個(gè)單例的實(shí)例语卤,這就違背了單例模式的設(shè)計(jì)出初衷,因此必須添加synchronized進(jìn)行同步,正確寫(xiě)法如下:

class Single {
    private static Single s = null;

    private Single() {
    }

    public static Single getInstance() {
        if (s == null) {
            synchronized (Single.class) {
                if (s == null)
                    s = new Single();
            }
        }
        return s;
    }
}

懶漢式與餓漢式的區(qū)別在于懶漢式用于延時(shí)加載粹舵。

而懶漢式出現(xiàn)的問(wèn)題在于使用多線程的時(shí)候創(chuàng)建多個(gè)實(shí)例钮孵,這時(shí)可以使用同步解決。

使用synchronized同步也是有技巧的眼滤,如果使用同步方法也是可以的巴席,代碼如下:

    public synchronized static Single getInstance() {
        if (s == null) {                
                if (s == null)
                    s = new Single();
            }            
        return s;
    }

但這種寫(xiě)法會(huì)導(dǎo)致效率稍微低下,因此一般都采用雙重判斷的同步代碼塊的寫(xiě)法诅需。

同時(shí)如果是使用了static靜態(tài)符合漾唉,靜態(tài)代碼塊所使用的同步鎖為該類所屬的字節(jié)碼對(duì)象!

死鎖

產(chǎn)生死鎖的四個(gè)必要條件:

(1) 互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用堰塌。
(2) 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí)赵刑,對(duì)已獲得的資源保持不放。
(3) 不剝奪條件:進(jìn)程已獲得的資源场刑,在末使用完之前般此,不能強(qiáng)行剝奪。
(4) 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系牵现。

同步中嵌套同步铐懊,但是它們之間的鎖卻不同,容易導(dǎo)致死鎖瞎疼,下面是死鎖示例:

public DeadLockTest TicketDemo2 {

    public static void main(String[] args) {
        Thread t1 = new Thread(new Test(true));
        Thread t2 = new Thread(new Test(false));
        t1.start();
        t2.start();
    }

}

class Test implements Runnable {
    private boolean flag;

    Test(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        if (flag) {
            while (true) {
                synchronized (MyLock.locka) {
                    System.out.println(Thread.currentThread().getName() + "...if locka ");
                    synchronized (MyLock.lockb) {
                        System.out.println(Thread.currentThread().getName() + "..if lockb");
                    }
                }
            }
        } else {
            while (true) {
                synchronized (MyLock.lockb) {
                    System.out.println(Thread.currentThread().getName() + "..else lockb");
                    synchronized (MyLock.locka) {
                        System.out.println(Thread.currentThread().getName() + ".....else locka");
                    }
                }
            }
        }
    }
}

class MyLock {
    static Object locka = new Object();
    static Object lockb = new Object();
}

在上面的例子中Test在t1線程中進(jìn)入if語(yǔ)句得到了MyLock.locka的鎖科乎,然后進(jìn)入了第二個(gè)代碼塊中需要MyLock.lockb的鎖才能進(jìn)行下一步。

但是此時(shí)t2線程也開(kāi)始執(zhí)行了贼急,它先進(jìn)入了Test中的else語(yǔ)句茅茂,獲得了MyLock.locka的鎖,需要MyLock.locka的所完成同步代碼竿裂。

此時(shí)陷入了死局玉吁,t1持有了MyLock.locka的鎖,但是無(wú)法獲得MyLock.lockb的鎖執(zhí)行完線程腻异,也無(wú)法釋放MyLock.locka的鎖进副。

而t2線程同樣如此,持有MyLock.lockb的鎖悔常,但是無(wú)法獲得MyLock.locka的鎖執(zhí)行完線程影斑,也無(wú)法釋放MyLock.locka的鎖。

在寫(xiě)程序的時(shí)候應(yīng)該避免死鎖的產(chǎn)生机打。

線程間通信

線程間通訊: 其實(shí)就是多個(gè)線程在操作同一個(gè)資源矫户,但是操作的動(dòng)作不同。

wait:
notify();
notifyAll();

notify()方法用于喚醒等待中的線程残邀,一般線程使用了wait()方法皆辽,會(huì)進(jìn)入線程池中等待執(zhí)行柑蛇,此時(shí)使用notify()方法一般喚醒線程池中的第一個(gè)等待線程。

都使用在同步中驱闷,因?yàn)橐獙?duì)持有監(jiān)視器(鎖)的線程操作耻台。
所以要使用在同步中,因?yàn)橹挥型讲啪哂墟i空另。

為什么這些操作線程的方法要定義Object類中呢盆耽?
因?yàn)檫@些方法在操作同步中線程時(shí),都必須要標(biāo)識(shí)它們所操作線程所持有的鎖扼菠,
只有同一個(gè)鎖上的被等待線程摄杂,可以被同一個(gè)鎖上notify喚醒。
不可以對(duì)不同鎖中的線程進(jìn)行喚醒循榆。

也就是說(shuō)析恢,等待和喚醒必須是同一個(gè)鎖。

而鎖可以是任意對(duì)象冯痢,所以可以被任意對(duì)象調(diào)用的方法定義Object類中氮昧。

多線程生產(chǎn)消費(fèi)者示例

下列代碼為正確示例:

class ProducerConsumerDemo {
    public static void main(String[] args) {
        Resource r = new Resource();

        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

/*
 * 對(duì)于多個(gè)生產(chǎn)者和消費(fèi)者。 為什么要定義while判斷標(biāo)記浦楣。 原因:讓被喚醒的線程再一次判斷標(biāo)記袖肥。
 * 為什么定義notifyAll, 因?yàn)樾枰獑拘褜?duì)方線程振劳。 因?yàn)橹挥胣otify椎组,容易出現(xiàn)只喚醒本方線程的情況。導(dǎo)致程序中的所有線程都等待历恐。
 */

class Resource {
    private String name;
    private int count = 1;
    private boolean flag = false;

    // t1 t2
    public synchronized void set(String name) {
        while (flag)
            try {this.wait();} catch (Exception e) {} // t1(放棄資格) t2(獲取資格)
        this.name = name + "--" + count++;

        System.out.println(Thread.currentThread().getName() + "...生產(chǎn)者.." + this.name);
        flag = true;
        this.notifyAll();
    }

    // t3 t4
    public synchronized void out() {
        while (!flag)
            try { wait();} catch (Exception e) {} // t3(放棄資格) t4(放棄資格)
        System.out.println(Thread.currentThread().getName() + "...消費(fèi)者........." + this.name);
        flag = false;
        this.notifyAll();
    }
}

class Producer implements Runnable {
    private Resource res;

    Producer(Resource res) {
        this.res = res;
    }

    public void run() {
        while (true) {
            res.set("+商品+");
        }
    }
}

class Consumer implements Runnable {
    private Resource res;

    Consumer(Resource res) {
        this.res = res;
    }

    public void run() {
        while (true) {
            res.out();
        }
    }
}

在多線程的情況下寸癌,通用的做法是使用while循環(huán) + notifyAll()喚醒方法來(lái)判斷執(zhí)行代碼。

而在單生產(chǎn)者和單消費(fèi)者的情況下使用if循環(huán) + notify()來(lái)判斷執(zhí)行代碼弱贼。

首先分析一下使用if循環(huán) + notify()在多生產(chǎn)者和多消費(fèi)者中產(chǎn)生的問(wèn)題蒸苇,使用這種方法會(huì)導(dǎo)致程序執(zhí)行兩次生產(chǎn),一次消費(fèi)的情況吮旅,又或者是兩次消費(fèi)一次生產(chǎn)的情況溪烤。

關(guān)鍵代碼如下:

public synchronized void set(String name)
{
    if(flag)
        try{this.wait();}catch(Exception e){}//t1(放棄資格)  t2(獲取資格)
    this.name = name+"--"+count++;

    System.out.println(Thread.currentThread().getName()+"...生產(chǎn)者.."+this.name);
    flag = true;
    this.notify();
}

//  t3   t4  
public synchronized void out()
{
    if(!flag)
        try{wait();}catch(Exception e){}//t3(放棄資格) t4(放棄資格)
    System.out.println(Thread.currentThread().getName()+"...消費(fèi)者........."+this.name);
    flag = false;
    this.notify();
}

產(chǎn)生這種情況的原因在于如果生產(chǎn)者(t1/t2)先獲得了執(zhí)行權(quán),此時(shí)flag為false庇勃,因此t1直接往下執(zhí)行檬嘀,生產(chǎn)出一個(gè)產(chǎn)品(執(zhí)行println),然后將flag設(shè)為true责嚷,但是此時(shí)t1仍然能繼續(xù)執(zhí)行(本案例中線程沒(méi)有退出機(jī)制鸳兽,因此一旦執(zhí)行將持續(xù)執(zhí)行不停止),于是t1再次在if語(yǔ)句中判斷罕拂,然而flag已經(jīng)為true揍异,所以t1執(zhí)行了wait()方法全陨,放棄執(zhí)行權(quán)。

重點(diǎn)來(lái)了蒿秦,在這個(gè)時(shí)候t2生產(chǎn)者烤镐,t3/t4消費(fèi)者都有執(zhí)行權(quán),假設(shè)這時(shí)t2取得執(zhí)行權(quán)棍鳖,執(zhí)行下去了,此時(shí)flag仍為true碗旅,因此t2也進(jìn)如wait()階段渡处。

然后就輪到t3/t4執(zhí)行了,他們消費(fèi)一次之后祟辟,將flag設(shè)為false医瘫,然后喚醒了在線程池中的線程,而notify()會(huì)喚醒處于線程池中的第一個(gè)等待的線程旧困,也就是t1醇份。然而此時(shí)t3仍舊執(zhí)行,但是遇到flag為false吼具,于是t3執(zhí)行了wait()方法僚纷。

這時(shí)線程中擁有執(zhí)行權(quán)的就剩下生產(chǎn)者t1和消費(fèi)者t4了,然而計(jì)算機(jī)執(zhí)行了t4線程拗盒,因?yàn)閒lag為false的緣故怖竭,t4也等待了。

此時(shí)在線程池中等待的順序依次為 t2陡蝇,t3痊臭,t4。

t1開(kāi)始執(zhí)行了登夫,然后把flag設(shè)置為true广匙,并且執(zhí)行了notify()方法,喚醒了t2線程恼策。

但是之前t2已經(jīng)通過(guò)了if的判斷鸦致,處于wait()狀態(tài),因此被喚醒的時(shí)候不在執(zhí)行if判斷戏蔑,直接往下執(zhí)行蹋凝,因此在t1之后也執(zhí)行了生產(chǎn)命令。

最后喚醒了t3消費(fèi)者总棵,然后繼續(xù)執(zhí)行鳍寂。

這就是聯(lián)系兩次執(zhí)行了生產(chǎn)命令的原因。

上面說(shuō)的有點(diǎn)啰嗦情龄,但這非常重要迄汛,需要細(xì)細(xì)體會(huì)捍壤。

了解了為什么在多生產(chǎn)/消費(fèi)者執(zhí)行的情況下,使用if判斷和notify()方法喚醒會(huì)持續(xù)執(zhí)行兩次生產(chǎn)或者消費(fèi)的命令后鞍爱,這是因?yàn)閕f只判斷一次flag的情況鹃觉,當(dāng)線程被喚醒之后會(huì)直接往下執(zhí)行的緣故。

那么我們?nèi)绻褂脀hile加上notify()方法睹逃,讓線程每次被喚醒的時(shí)候都進(jìn)行判斷呢盗扇?

這時(shí)產(chǎn)生了死鎖。

這個(gè)結(jié)束比較簡(jiǎn)單沉填,首先t1運(yùn)行疗隶,flag為true,然后t1繼續(xù)執(zhí)行的時(shí)候就進(jìn)入wait()方法翼闹,如果此時(shí)t2執(zhí)行的話斑鼻,也進(jìn)入wait()方法中。

然后t3/t4執(zhí)行了猎荠,t3將flag設(shè)為false坚弱,并喚醒了t1,然后t3繼續(xù)執(zhí)行便執(zhí)行了wait()方法关摇,如果此時(shí)t4執(zhí)行了荒叶,也會(huì)進(jìn)入wait()方法中,這時(shí)消費(fèi)者線程全滅

最后剩下t1在執(zhí)行了拒垃,t1將flag設(shè)置true之后停撞,喚醒了t2,但是此時(shí)flag為true悼瓮,而每次喚醒都會(huì)在while進(jìn)行一次循環(huán)戈毒,此時(shí)悲催的t2再次進(jìn)入wait()方法,生產(chǎn)者線程也全滅了横堡!

因?yàn)橹挥胣otify埋市,容易出現(xiàn)只喚醒本方線程的情況。導(dǎo)致程序中的所有線程都等待命贴。

這就是產(chǎn)生死鎖的原因道宅。

所以在多生產(chǎn)多消費(fèi)者的情況下使用while讓每個(gè)線程在喚醒的時(shí)候再次進(jìn)行判斷,然后使用notifyAll()喚醒所有的線程胸蛛,讓每個(gè)線程再次做判斷是否繼續(xù)往下執(zhí)行污茵,這樣才能確保每次都有線程能夠執(zhí)行,也能確保每個(gè)線程都能被喚醒葬项,不會(huì)導(dǎo)致死鎖泞当。

使用jdk1.5之后的Lock接口進(jìn)行同步

上面生產(chǎn)者和消費(fèi)者的使用方法太過(guò)繁瑣了,JDK1.5 中提供了多線程升級(jí)解決方案民珍。
將同步Synchronized替換成現(xiàn)實(shí)Lock操作襟士。
將Object中的wait盗飒,notify notifyAll,替換了Condition對(duì)象陋桂。該對(duì)象可以Lock鎖 進(jìn)行獲取逆趣。
在下列示例中,實(shí)現(xiàn)了本方只喚醒對(duì)方操作嗜历。

Lock:替代了Synchronized
    lock 
    unlock
    newCondition()

Condition:替代了Object wait notify notifyAll
    await();
    signal();
    signalAll();

我們修改上面生產(chǎn)者和消費(fèi)者案例中的Resource的代碼宣渗,將Synchronized替換為L(zhǎng)ock,將notifyAll替換為Condition秸脱,代碼如下:

class Resource {
    private String name;
    private int count = 1;
    private boolean flag = false;
    // t1 t2
    private Lock lock = new ReentrantLock();

    private Condition condition_pro = lock.newCondition();
    private Condition condition_con = lock.newCondition();

    public void set(String name) throws InterruptedException {
        lock.lock();
        try {
            while (flag)
                condition_pro.await();// t1,t2等待
            this.name = name + "--" + count++;

            System.out.println(Thread.currentThread().getName() + "...生產(chǎn)者.." + this.name);
            flag = true;
            condition_con.signal(); // 喚醒t3落包,t4中的一個(gè)
        } finally {
            lock.unlock();// 釋放鎖的動(dòng)作一定要執(zhí)行。
        }
    }

    // t3 t4
    public void out() throws InterruptedException {
        lock.lock();
        try {
            while (!flag)
                condition_con.await();
            System.out.println(Thread.currentThread().getName() + "...消費(fèi)者........." + this.name);
            flag = false;
            condition_pro.signal();
        } finally {
            lock.unlock();
        }

    }
}

class Producer implements Runnable {
    private Resource res;

    Producer(Resource res) {
        this.res = res;
    }

    public void run() {
        while (true) {
            try {
                res.set("+商品+");
            } catch (InterruptedException e) {
            }

        }
    }
}

class Consumer implements Runnable {
    private Resource res;

    Consumer(Resource res) {
        this.res = res;
    }

    public void run() {
        while (true) {
            try {
                res.out();
            } catch (InterruptedException e) {
            }
        }
    }
}

使用Condition的好處在于一個(gè)Lock鎖可以擁有多個(gè)Condition對(duì)象摊唇。

而在上面的代碼中定義了兩個(gè)Condition對(duì)象,一個(gè)為生產(chǎn)者condition_pro的條件涯鲁,另一個(gè)為消費(fèi)者condition_con條件巷查。

這使condition_pro可以使用自己的await()方法和condition_pro.signal()喚醒方法,這樣就能讓在生產(chǎn)者中喚醒消費(fèi)者抹腿,而在消費(fèi)者中喚醒生產(chǎn)者岛请,不會(huì)發(fā)生像Synchornized中的那種喚醒了本方線程的失誤事件。

但是要注意警绩,使用Lock的時(shí)候崇败,一定要在finally中釋放鎖,即調(diào)用lock.unlock()方法肩祥。

停止線程的方式

stop方法已經(jīng)過(guò)時(shí)后室。

如何停止線程?
只有一種混狠,run方法結(jié)束岸霹。
開(kāi)啟多線程運(yùn)行,運(yùn)行代碼通常是循環(huán)結(jié)構(gòu)将饺。

只要控制住循環(huán)贡避,就可以讓run方法結(jié)束,也就是線程結(jié)束予弧。

示例代碼中給Runnable對(duì)象添加了flag標(biāo)記刮吧,控制了while循環(huán),代碼如下:

class StopThreadDemo {
    public static void main(String[] args) {
        StopThread st = new StopThread();
        
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.start();
        t2.start();

        int num = 0;

        while (true) {
            if (num++ == 60) {    
                st.changeFlag();
                t1.interrupt();
                t2.interrupt();
                break;
            }
            System.out.println(Thread.currentThread().getName() + "......." + num);
        }
        System.out.println("over");

    }
}

class StopThread implements Runnable {
    private boolean flag = true;
    
    public void run() {
        while (flag) {    
            System.out.println(Thread.currentThread().getName() + "....run");
        }
    }

    public void changeFlag() {
        flag = false;
    }
}

特殊情況:
當(dāng)線程處于了凍結(jié)狀態(tài)掖蛤。
就不會(huì)讀取到標(biāo)記杀捻。那么線程就不會(huì)結(jié)束。

當(dāng)沒(méi)有指定的方式讓凍結(jié)的線程恢復(fù)到運(yùn)行狀態(tài)是坠七,這時(shí)需要對(duì)凍結(jié)進(jìn)行清除水醋。
強(qiáng)制讓線程恢復(fù)到運(yùn)行狀態(tài)中來(lái)旗笔,這樣就可以操作標(biāo)記讓線程結(jié)束。Thread類提供該方法 interrupt();

示例如下:

class StopThread implements Runnable {
    private boolean flag = true;
    
    public synchronized void run() {
        while (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "....Exception");
                flag = false;
            }
            System.out.println(Thread.currentThread().getName() + "....run");
        }
    }

    public void changeFlag() {
        flag = false;
    }
}

class StopThreadDemo {
    public static void main(String[] args) {
        StopThread st = new StopThread();
        
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.start();
        t2.start();

        int num = 0;

        while (true) {
            if (num++ == 60) {                    
                t1.interrupt();
                t2.interrupt();
                break;
            }
            System.out.println(Thread.currentThread().getName() + "......." + num);
        }
        System.out.println("over");

    }
}

要注意的是使用 interrupt();方法并不是正確的停止線程的方式拄踪,而是以拋出異常的方式停止線程蝇恶,要慎用。

Thread中的其他方法

setDaemon(boolean on) 將該線程標(biāo)記為守護(hù)線程或用戶線程惶桐。簡(jiǎn)單的說(shuō)就是將線程設(shè)置為后臺(tái)線程撮弧,此時(shí)將會(huì)與主線程搶奪CPU資源,而且主線程結(jié)束時(shí)姚糊,后臺(tái)線程會(huì)自動(dòng)結(jié)束贿衍。

修改上面的StopThreadDemo代碼,將t1/t2線程設(shè)置為后臺(tái)線程救恨,如下

class StopThreadDemo {
    public static void main(String[] args) {
        StopThread st = new StopThread();
        
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();

        int num = 0;

        while (true) {
            if (num++ == 60) {                    
                
                break;
            }
            System.out.println(Thread.currentThread().getName() + "......." + num);
        }
        System.out.println("over");

    }
}

class StopThread implements Runnable {
    private boolean flag = true;
    
    public void run() {
        while (flag) {    
            System.out.println(Thread.currentThread().getName() + "....run");
        }
    }

    public void changeFlag() {
        flag = false;
    }
}

此時(shí)并沒(méi)有使用任何的方式結(jié)束線程贸辈,卻發(fā)現(xiàn)當(dāng)主線程結(jié)束時(shí),t1/t2線程也結(jié)束了肠槽。

join()方法與yield()方法

join:
當(dāng)A線程執(zhí)行到了B線程的.join()方法時(shí)擎淤,A就會(huì)等待。等B線程都執(zhí)行完秸仙,A才會(huì)執(zhí)行嘴拢。

join可以用來(lái)臨時(shí)加入線程執(zhí)行,代碼示例:

class JoinDemo {
    public static void main(String[] args) {
        Demo d = new Demo();
        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        t1.start();

         try {
            t1.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        t2.start();

        for (int x = 0; x < 80; x++) {
             System.out.println("main....."+x);
        }
        System.out.println("over");
    }
}

class Demo implements Runnable {
    public void run() {
        for (int x = 0; x < 70; x++) {
            System.out.println(Thread.currentThread().toString() + "....." + x);
        }
    }
}

toString():返回該線程的字符串你表示形式寂纪,包括線程名稱席吴、優(yōu)先級(jí)和線程組

yield():暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程捞蛋,代碼示例:

class YieldDemo {
    public static void main(String[] args) {
        Demo d = new Demo();
        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        t1.start();


        t2.start();

        System.out.println("over");
    }
}

class Demo implements Runnable {
    public void run() {
        for (int x = 0; x < 70; x++) {
            System.out.println(Thread.currentThread().toString() + "....." + x);
            Thread.yield();
        }
    }
}

線程間通訊

Java線程間通訊其實(shí)也就是使用Synchronized和Object類方法wait(),notify(),notifyAll()的共同使用孝冒,保證運(yùn)行結(jié)果正確,這些都在上述代碼有所涉及襟交。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迈倍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捣域,更是在濱河造成了極大的恐慌啼染,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焕梅,死亡現(xiàn)場(chǎng)離奇詭異迹鹅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)贞言,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)斜棚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事弟蚀≡橄迹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵义钉,是天一觀的道長(zhǎng)昧绣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捶闸,這世上最難降的妖魔是什么儡司? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任椭符,我火速辦了婚禮脚翘,結(jié)果婚禮上徘键,老公的妹妹穿的比我還像新娘。我一直安慰自己央碟,他們只是感情好税灌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著亿虽,像睡著了一般垄琐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上经柴,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音墩朦,去河邊找鬼坯认。 笑死,一個(gè)胖子當(dāng)著我的面吹牛氓涣,可吹牛的內(nèi)容都是我干的牛哺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼劳吠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼引润!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起痒玩,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤淳附,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蠢古,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奴曙,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年草讶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洽糟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖坤溃,靈堂內(nèi)的尸體忽然破棺而出拍霜,到底是詐尸還是另有隱情,我是刑警寧澤薪介,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布祠饺,位于F島的核電站,受9級(jí)特大地震影響昭灵,放射性物質(zhì)發(fā)生泄漏吠裆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一烂完、第九天 我趴在偏房一處隱蔽的房頂上張望试疙。 院中可真熱鬧,春花似錦抠蚣、人聲如沸祝旷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怀跛。三九已至,卻和暖如春柄冲,著一層夾襖步出監(jiān)牢的瞬間吻谋,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工现横, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漓拾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓戒祠,卻偏偏與公主長(zhǎng)得像骇两,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子姜盈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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