java多線程學習筆記

進程:正在執行的程序,是一個動態的過程

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

進程中至少有一個線程。

在Java VM(java虛擬機)啟動的時候會有一個進程java.exe.

該進程中至少一個線程負責java程序的執行。
而且這個線程運行的代碼存在于main方法中。
該線程稱之為主線程。

擴展:其實更細節說明jvm,jvm啟動不止一個線程,還有負責垃圾回收機制的線程。

創建線程

創建線程的第一種方式:繼承Thread類

步驟:

1,定義類繼承Thread。
2,復寫Thread類中的run方法。
    目的:將自定義代碼存儲在run方法。讓線程運行。

3,調用線程的start方法,
    該方法兩個作用:啟動線程,調用run方法。

發現運行結果每一次都不同。因為多個線程都獲取cpu的執行權。cpu執行到誰,誰就運行。
明確一點,在某一個時刻,只能有一個程序在運行(多核除外)。cpu在做著快速的切換,以達到看上去是同時運行的效果。

我們可以形象把多線程的運行形容為在互相搶奪cpu的執行權。

這就是多線程的一個特性:隨機性。誰搶到誰執行,至于執行多長,cpu說的算。

為什么要覆蓋run方法呢?

Thread類用于描述線程。該類就定義了一個功能,用于存儲其他線程(非主線程)要運行的代碼。該存儲功能就是run方法。

也就是說Thread類中的run方法,用于存儲線程要運行的代碼。

創建線程的第二種方式:實現Runable接口

步驟:

1,定義類實現Runnable接口
2,覆蓋Runnable接口中的run方法。
    將線程要運行的代碼存放在該run方法中。

3,通過Thread類建立線程對象。
4,將Runnable接口的子類對象作為實際參數傳遞給Thread類的構造函數。
    為什么要將Runnable接口的子類對象傳遞給Thread的構造函數。
    因為,自定義的run方法所屬的對象是Runnable接口的子類對象。
    所以要讓線程去指定指定對象的run方法。就必須明確該run方法所屬對象。


5,調用Thread類的start方法開啟線程并調用Runnable接口子類的run方法。

兩種創建現場的區別

實現方式和繼承方式有什么區別呢?

實現方式好處:避免了單繼承的局限性。
在定義線程時,建立使用實現方式。

兩種方式區別:

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

實現Runnable,線程代碼存在接口的子類的run方法。

線程中的方法

線程都有自己默認的名稱:Thread-編號 該編號從0開始。

線程中的常用方法

static Thread currentThread():獲取當前線程對象。
getName(): 獲取線程名稱。

設置線程名稱:setName或者構造函數。

多線程中出現的問題

多線程的運行出現了安全問題。

問題的原因:

當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,
另一個線程參與進來執行。導致共享數據的錯誤。

解決辦法:

對多條操作共享數據的語句,只能讓一個線程都執行完。在執行過程中,其他線程不可以參與執行。

Java對于多線程的安全問題提供了專業的解決方式。

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

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

}

對象如同鎖。持有鎖的線程可以在同步中執行。
沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因為沒有獲取鎖。

同步的前提:

1,必須要有兩個或者兩個以上的線程。
2,必須是多個線程使用同一個鎖。

必須保證同步中只能有一個線程在運行。

好處:解決了多線程的安全問題。

弊端:多個線程都需要判斷鎖,較為消耗資源,

示例代碼如下:

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提供對象

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

此段代碼的是模擬賣票,假設有100張票,然后有4個線程同時賣票。
可以在Ticket類中有synchronized同步代碼塊,當有同步代碼塊時,持有synchronized同步鎖的線程可以在同步中執行。沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因為沒有獲取synchronized同步鎖。線程將依序執行,也就是有一個線程在進入時,另一個線程無法進入。

如果沒有synchronized同步鎖,那么有可能會有以下問題產生,當tick剩下1張票的時候,t1現場進入了,然后遇到Thread.sleep(50),失去執行權。

接著t2線程執行了了,此時tick仍然是1張票,因此t2也進入if語句,也遇到sleep()方法,失去了執行權。

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

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

最后t1開始執行了,tick為0了。但是此時t2也在if語句中,也執行語句,tick就為-1,然后是t3/t4,導致了代碼運行的結果出現了負數,與設想中的不同!

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

同步鎖

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

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

那么問題來了,在同步代碼塊中我們可以很清楚的看到synchronized給哪一個對象上鎖,那么同步方法又是給誰上鎖呢?

同步函數用的是哪一個鎖呢?
函數需要被對象調用。那么函數都有一個所屬對象引用,就是this,所以同步函數使用的鎖是this。

通過該程序進行驗證。

使用兩個線程來買票。一個線程在同步代碼塊中。一個線程在同步函數中。都在執行買票動作。

代碼如下:

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();
        // 讓主線程休眠,將執行權移交給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();
    }
    // 同步方法的鎖對象是this,即所屬對象的引用
    public synchronized void show()    {  
        if (tick > 0) {
            try{Thread.sleep(10);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() + "....show.... : " + tick--);
        }
    }
}

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

然后再控制臺中看到結果:

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

這里居然賣出了0張票,這明顯是一個錯誤的結果,這是為什么呢?

答案在于我們雖然在多線程中使用了同步鎖,但是我們的鎖對象并不是同一個對象,因為在同步代碼塊中使用的是obj對象,如下:

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

但是當obj替換成this的時候,我們就能輸出正確的結果,不在輸出0張票。

這足以證明在同步方法中使用的鎖對象就是所屬對象引用。

靜態同步方法

static實際上也能使用同步鎖,我們將上述代碼修改,首先將同步方法修改為靜態同步方法,然后將tick也標識為static,代碼如下:

private static  int tick = 100;
···

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

調用方法跟前面一樣。

這時再次輸出了tick=0的錯誤結果!

這又是為什么呢?

這是因為靜態方法中的同步鎖對象使用的不是this,因為靜態方法中也不可以定義this。

其原因在于靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。
即 類名.class 該對象的類型是Class

靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象。 類名.class。

所以我們將同步代碼塊中的所對象修改為Ticket.class時就能輸出正確結果,此時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();
    }
    // 靜態方法的所對象是    類名.class
    public static synchronized void show()    {  
        if (tick > 0) {
            try{Thread.sleep(50);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() + "....show.... : " + tick--);
        }
    }
}

單例模式中的懶漢式

單例模式中的懶漢式寫法其實頗為復雜,因為要考慮到多線程的問題。如果不添加synchronized進行同步,在多線程的情況下,仍有可能導致創建了多個單例的實例,這就違背了單例模式的設計出初衷,因此必須添加synchronized進行同步,正確寫法如下:

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;
    }
}

懶漢式與餓漢式的區別在于懶漢式用于延時加載。

而懶漢式出現的問題在于使用多線程的時候創建多個實例,這時可以使用同步解決。

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

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

但這種寫法會導致效率稍微低下,因此一般都采用雙重判斷的同步代碼塊的寫法。

同時如果是使用了static靜態符合,靜態代碼塊所使用的同步鎖為該類所屬的字節碼對象!

死鎖

產生死鎖的四個必要條件:

(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。

同步中嵌套同步,但是它們之間的鎖卻不同,容易導致死鎖,下面是死鎖示例:

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線程中進入if語句得到了MyLock.locka的鎖,然后進入了第二個代碼塊中需要MyLock.lockb的鎖才能進行下一步。

但是此時t2線程也開始執行了,它先進入了Test中的else語句,獲得了MyLock.locka的鎖,需要MyLock.locka的所完成同步代碼。

此時陷入了死局,t1持有了MyLock.locka的鎖,但是無法獲得MyLock.lockb的鎖執行完線程,也無法釋放MyLock.locka的鎖。

而t2線程同樣如此,持有MyLock.lockb的鎖,但是無法獲得MyLock.locka的鎖執行完線程,也無法釋放MyLock.locka的鎖。

在寫程序的時候應該避免死鎖的產生。

線程間通信

線程間通訊: 其實就是多個線程在操作同一個資源,但是操作的動作不同。

wait:
notify();
notifyAll();

notify()方法用于喚醒等待中的線程,一般線程使用了wait()方法,會進入線程池中等待執行,此時使用notify()方法一般喚醒線程池中的第一個等待線程。

都使用在同步中,因為要對持有監視器(鎖)的線程操作。
所以要使用在同步中,因為只有同步才具有鎖。

為什么這些操作線程的方法要定義Object類中呢?
因為這些方法在操作同步中線程時,都必須要標識它們所操作線程所持有的鎖,
只有同一個鎖上的被等待線程,可以被同一個鎖上notify喚醒。
不可以對不同鎖中的線程進行喚醒。

也就是說,等待和喚醒必須是同一個鎖。

而鎖可以是任意對象,所以可以被任意對象調用的方法定義Object類中。

多線程生產消費者示例

下列代碼為正確示例:

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

    }
}

/*
 * 對于多個生產者和消費者。 為什么要定義while判斷標記。 原因:讓被喚醒的線程再一次判斷標記。
 * 為什么定義notifyAll, 因為需要喚醒對方線程。 因為只用notify,容易出現只喚醒本方線程的情況。導致程序中的所有線程都等待。
 */

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() + "...生產者.." + 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() + "...消費者........." + 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循環 + notifyAll()喚醒方法來判斷執行代碼。

而在單生產者和單消費者的情況下使用if循環 + notify()來判斷執行代碼。

首先分析一下使用if循環 + notify()在多生產者和多消費者中產生的問題,使用這種方法會導致程序執行兩次生產,一次消費的情況,又或者是兩次消費一次生產的情況。

關鍵代碼如下:

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()+"...生產者.."+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()+"...消費者........."+this.name);
    flag = false;
    this.notify();
}

產生這種情況的原因在于如果生產者(t1/t2)先獲得了執行權,此時flag為false,因此t1直接往下執行,生產出一個產品(執行println),然后將flag設為true,但是此時t1仍然能繼續執行(本案例中線程沒有退出機制,因此一旦執行將持續執行不停止),于是t1再次在if語句中判斷,然而flag已經為true,所以t1執行了wait()方法,放棄執行權。

重點來了,在這個時候t2生產者,t3/t4消費者都有執行權,假設這時t2取得執行權,執行下去了,此時flag仍為true,因此t2也進如wait()階段。

然后就輪到t3/t4執行了,他們消費一次之后,將flag設為false,然后喚醒了在線程池中的線程,而notify()會喚醒處于線程池中的第一個等待的線程,也就是t1。然而此時t3仍舊執行,但是遇到flag為false,于是t3執行了wait()方法。

這時線程中擁有執行權的就剩下生產者t1和消費者t4了,然而計算機執行了t4線程,因為flag為false的緣故,t4也等待了。

此時在線程池中等待的順序依次為 t2,t3,t4。

t1開始執行了,然后把flag設置為true,并且執行了notify()方法,喚醒了t2線程。

但是之前t2已經通過了if的判斷,處于wait()狀態,因此被喚醒的時候不在執行if判斷,直接往下執行,因此在t1之后也執行了生產命令。

最后喚醒了t3消費者,然后繼續執行。

這就是聯系兩次執行了生產命令的原因。

上面說的有點啰嗦,但這非常重要,需要細細體會。

了解了為什么在多生產/消費者執行的情況下,使用if判斷和notify()方法喚醒會持續執行兩次生產或者消費的命令后,這是因為if只判斷一次flag的情況,當線程被喚醒之后會直接往下執行的緣故。

那么我們如果使用while加上notify()方法,讓線程每次被喚醒的時候都進行判斷呢?

這時產生了死鎖。

這個結束比較簡單,首先t1運行,flag為true,然后t1繼續執行的時候就進入wait()方法,如果此時t2執行的話,也進入wait()方法中。

然后t3/t4執行了,t3將flag設為false,并喚醒了t1,然后t3繼續執行便執行了wait()方法,如果此時t4執行了,也會進入wait()方法中,這時消費者線程全滅

最后剩下t1在執行了,t1將flag設置true之后,喚醒了t2,但是此時flag為true,而每次喚醒都會在while進行一次循環,此時悲催的t2再次進入wait()方法,生產者線程也全滅了!

因為只用notify,容易出現只喚醒本方線程的情況。導致程序中的所有線程都等待。

這就是產生死鎖的原因。

所以在多生產多消費者的情況下使用while讓每個線程在喚醒的時候再次進行判斷,然后使用notifyAll()喚醒所有的線程,讓每個線程再次做判斷是否繼續往下執行,這樣才能確保每次都有線程能夠執行,也能確保每個線程都能被喚醒,不會導致死鎖。

使用jdk1.5之后的Lock接口進行同步

上面生產者和消費者的使用方法太過繁瑣了,JDK1.5 中提供了多線程升級解決方案。
將同步Synchronized替換成現實Lock操作。
將Object中的wait,notify notifyAll,替換了Condition對象。該對象可以Lock鎖 進行獲取。
在下列示例中,實現了本方只喚醒對方操作。

Lock:替代了Synchronized
    lock 
    unlock
    newCondition()

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

我們修改上面生產者和消費者案例中的Resource的代碼,將Synchronized替換為Lock,將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() + "...生產者.." + this.name);
            flag = true;
            condition_con.signal(); // 喚醒t3,t4中的一個
        } finally {
            lock.unlock();// 釋放鎖的動作一定要執行。
        }
    }

    // t3 t4
    public void out() throws InterruptedException {
        lock.lock();
        try {
            while (!flag)
                condition_con.await();
            System.out.println(Thread.currentThread().getName() + "...消費者........." + 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的好處在于一個Lock鎖可以擁有多個Condition對象。

而在上面的代碼中定義了兩個Condition對象,一個為生產者condition_pro的條件,另一個為消費者condition_con條件。

這使condition_pro可以使用自己的await()方法和condition_pro.signal()喚醒方法,這樣就能讓在生產者中喚醒消費者,而在消費者中喚醒生產者,不會發生像Synchornized中的那種喚醒了本方線程的失誤事件。

但是要注意,使用Lock的時候,一定要在finally中釋放鎖,即調用lock.unlock()方法。

停止線程的方式

stop方法已經過時。

如何停止線程?
只有一種,run方法結束。
開啟多線程運行,運行代碼通常是循環結構。

只要控制住循環,就可以讓run方法結束,也就是線程結束。

示例代碼中給Runnable對象添加了flag標記,控制了while循環,代碼如下:

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;
    }
}

特殊情況:
當線程處于了凍結狀態。
就不會讀取到標記。那么線程就不會結束。

當沒有指定的方式讓凍結的線程恢復到運行狀態是,這時需要對凍結進行清除。
強制讓線程恢復到運行狀態中來,這樣就可以操作標記讓線程結束。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) 將該線程標記為守護線程或用戶線程。簡單的說就是將線程設置為后臺線程,此時將會與主線程搶奪CPU資源,而且主線程結束時,后臺線程會自動結束。

修改上面的StopThreadDemo代碼,將t1/t2線程設置為后臺線程,如下

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;
    }
}

此時并沒有使用任何的方式結束線程,卻發現當主線程結束時,t1/t2線程也結束了。

join()方法與yield()方法

join:
當A線程執行到了B線程的.join()方法時,A就會等待。等B線程都執行完,A才會執行。

join可以用來臨時加入線程執行,代碼示例:

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():返回該線程的字符串你表示形式,包括線程名稱、優先級和線程組

yield():暫停當前正在執行的線程對象,并執行其他線程,代碼示例:

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線程間通訊其實也就是使用Synchronized和Object類方法wait(),notify(),notifyAll()的共同使用,保證運行結果正確,這些都在上述代碼有所涉及。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容