java多線程

<pre>

理解程序、進(jìn)程、線程的概念

程序可以理解為靜態(tài)的代碼

進(jìn)程可以理解為執(zhí)行中的程序

線程可以理解為進(jìn)程的進(jìn)一步細(xì)分,程序的一條執(zhí)行路徑

使用多線程的優(yōu)點(diǎn):

提高應(yīng)用程序的響應(yīng)。對(duì)圖形化界面更有意義,可增強(qiáng)用戶體驗(yàn)。

提高計(jì)算機(jī)系統(tǒng)CPU的利用率

改善程序結(jié)構(gòu)。將既長(zhǎng)又復(fù)雜的進(jìn)程分為多個(gè)線程,獨(dú)立運(yùn)行,利于理解和修改

在java中要想實(shí)現(xiàn)多線程,有兩種手段,一種是繼續(xù)Thread類,另外一種是實(shí)現(xiàn)Runable接口

繼承java.lang.Thread類

下面來(lái)看一個(gè)簡(jiǎn)單的實(shí)例:

class Thread1 extends Thread{
    private String name;
    public Thread1(String name) {
       this.name=name;
    }
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "運(yùn)行  :  " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
public class Main {

    public static void main(String[] args) {
        Thread1 mTh1=new Thread1("A");
        Thread1 mTh2=new Thread1("B");
        mTh1.start();
        mTh2.start();

    }

}

輸出

輸出:
A運(yùn)行  :  0
B運(yùn)行  :  0
A運(yùn)行  :  1
A運(yùn)行  :  2
A運(yùn)行  :  3
A運(yùn)行  :  4
B運(yùn)行  :  1
B運(yùn)行  :  2
B運(yùn)行  :  3
B運(yùn)行  :  4
再運(yùn)行一下:
A運(yùn)行  :  0
B運(yùn)行  :  0
B運(yùn)行  :  1
B運(yùn)行  :  2
B運(yùn)行  :  3
B運(yùn)行  :  4
A運(yùn)行  :  1
A運(yùn)行  :  2
A運(yùn)行  :  3
A運(yùn)行  :  4

說(shuō)明:

程序啟動(dòng)運(yùn)行main時(shí)候,java虛擬機(jī)啟動(dòng)一個(gè)進(jìn)程,主線程main在main()調(diào)用時(shí)候被創(chuàng)建。隨著調(diào)用Thread1的兩個(gè)對(duì)象的start方法,另外兩個(gè)線程也啟動(dòng)了,這樣,整個(gè)應(yīng)用就在多線程下運(yùn)行。

以下是關(guān)系到線程運(yùn)行狀態(tài)的幾個(gè)方法:

1)start方法

start()用來(lái)啟動(dòng)一個(gè)線程,當(dāng)調(diào)用start方法后,系統(tǒng)才會(huì)開啟一個(gè)新的線程來(lái)執(zhí)行用戶定義的子任務(wù),在這個(gè)過程中,會(huì)為相應(yīng)的線程分配需要的資源。

2)run方法

run()方法是不需要用戶來(lái)調(diào)用的,當(dāng)通過start方法啟動(dòng)一個(gè)線程之后,當(dāng)線程獲得了CPU執(zhí)行時(shí)間,便進(jìn)入run方法體去執(zhí)行具體的任務(wù)。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執(zhí)行的任務(wù)。

3)sleep方法

  sleep相當(dāng)于讓線程睡眠,交出CPU,讓CPU去執(zhí)行其他的任務(wù)。

實(shí)現(xiàn)java.lang.Runnable接口

用Runnable也是非常常見的一種,我們只需要重寫run方法即可。下面也來(lái)看個(gè)實(shí)例:

class Thread2 implements Runnable{
    private String name;

    public Thread2(String name) {
        this.name=name;
    }

    @Override
    public void run() {
          for (int i = 0; i < 5; i++) {
                System.out.println(name + "運(yùn)行  :  " + i);
                try {
                    Thread.sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

    }

}
public class Main {

    public static void main(String[] args) {
        new Thread(new Thread2("C")).start();
        new Thread(new Thread2("D")).start();
    }

}

輸出

輸出:
C運(yùn)行  :  0
D運(yùn)行  :  0
D運(yùn)行  :  1
C運(yùn)行  :  1
D運(yùn)行  :  2
C運(yùn)行  :  2
D運(yùn)行  :  3
C運(yùn)行  :  3
D運(yùn)行  :  4
C運(yùn)行  :  4

說(shuō)明:

Thread2類通過實(shí)現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個(gè)約定。所有的多線程代碼都在run方法里面。Thread類實(shí)際上也是實(shí)現(xiàn)了Runnable接口的類。

在啟動(dòng)的多線程的時(shí)候,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對(duì)象,然后調(diào)用Thread對(duì)象的start()方法來(lái)運(yùn)行多線程代碼。

實(shí)際上所有的多線程代碼都是通過運(yùn)行Thread的start()方法來(lái)運(yùn)行的。因此,不管是擴(kuò)展Thread類還是實(shí)現(xiàn)Runnable接口來(lái)實(shí)現(xiàn)多線程,最終還是通過Thread的對(duì)象的API來(lái)控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)。

Thread和Runnable的區(qū)別

對(duì)比一下繼承的方式 vs 實(shí)現(xiàn)的方式

1.聯(lián)系:public class Thread implements Runnable(繼承的方式的Thread也實(shí)現(xiàn)了Runnable接口)

2.哪個(gè)方式好?

實(shí)現(xiàn)的方式優(yōu)于繼承的方式 why?

① 避免了java單繼承的局限性

② 如果多個(gè)線程要操作同一份資源(或數(shù)據(jù)),更適合使用實(shí)現(xiàn)的方式

看一個(gè)例子:

//模擬火車站售票窗口,開啟三個(gè)窗口售票,總票數(shù)為100張
//存在線程的安全問題
class Window extends Thread {
    int ticket = 100;

    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "售票,票號(hào)為:"+ ticket--);
            } else {
                break;
            }
        }
    }
}

public class TestWindow {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }

}

class Window implements Runnable {
     int ticket = 100;//要將全局變量聲明為靜態(tài),不然每個(gè)對(duì)象都有這個(gè)屬性,會(huì)賣出300張票

    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "售票,票號(hào)為:"+ ticket--);
            } else {
                break;
            }
        }
    }
}

public class Main {

    //模擬火車站售票窗口,開啟三個(gè)窗口售票,總票數(shù)為100張
//存在線程的安全問題

    public static void main(String[] args) {
        Window w1 = new Window();
        Thread t1 = new Thread(w1, "t1");
        Thread t2 = new Thread(w1, "t2");
        Thread t3 = new Thread(w1, "t3");

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

}

線程的生命周期

image

線程調(diào)度

1、調(diào)整線程優(yōu)先級(jí):Java線程有優(yōu)先級(jí),優(yōu)先級(jí)高的線程會(huì)獲得較多的運(yùn)行機(jī)會(huì)。

Java線程的優(yōu)先級(jí)用整數(shù)表示,取值范圍是1~10,Thread類有以下三個(gè)靜態(tài)常量:

static int MAX_PRIORITY

線程可以具有的最高優(yōu)先級(jí),取值為10。

static int MIN_PRIORITY

線程可以具有的最低優(yōu)先級(jí),取值為1。

static int NORM_PRIORITY

分配給線程的默認(rèn)優(yōu)先級(jí),取值為5。

Thread類的setPriority()和getPriority()方法分別用來(lái)設(shè)置和獲取線程的優(yōu)先級(jí)。

2、線程睡眠:Thread.sleep(long millis)方法,使線程轉(zhuǎn)到阻塞狀態(tài)。millis參數(shù)設(shè)定睡眠的時(shí)間,以毫秒為單位。當(dāng)睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。sleep()平臺(tái)移植性好。

3、線程等待:Object類中的wait()方法,導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 喚醒方法。

4、線程讓步:Thread.yield() 方法,暫停當(dāng)前正在執(zhí)行的線程對(duì)象,把執(zhí)行機(jī)會(huì)讓給相同或者更高優(yōu)先級(jí)的線程。

5、線程加入:join()方法,等待其他線程終止。在當(dāng)前線程中調(diào)用另一個(gè)線程的join()方法,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個(gè)進(jìn)程運(yùn)行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。

6、線程喚醒:Object類中的notify()方法,喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。

注意:Thread中suspend()和resume()兩個(gè)方法在JDK1.5中已經(jīng)廢除,不再介紹。因?yàn)橛兴梨i傾向。

代碼示例

join():指等待某線程終止。

為什么要用join()方法

在很多情況下,主線程生成并起動(dòng)了子線程,如果子線程里要進(jìn)行大量的耗時(shí)的運(yùn)算,主線程往往將于子線程之前結(jié)束,但是如果主線程處理完其他的事務(wù)后,需要用到子線程的處理結(jié)果,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束,這個(gè)時(shí)候就要用到j(luò)oin()方法了。

class Thread1 extends Thread{
    private String name;
    public Thread1(String name) {
        super(name);
       this.name=name;
    }
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 線程運(yùn)行開始!");
        for (int i = 0; i < 5; i++) {
            System.out.println("子線程"+name + "運(yùn)行 : " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 線程運(yùn)行結(jié)束!");
    }
}

public class Main {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"主線程運(yùn)行開始!");
        Thread1 mTh1=new Thread1("A");
        Thread1 mTh2=new Thread1("B");
        mTh1.start();
        mTh2.start();
        System.out.println(Thread.currentThread().getName()+ "主線程運(yùn)行結(jié)束!");

    }

}

輸出結(jié)果:
main主線程運(yùn)行開始!
main主線程運(yùn)行結(jié)束!
B 線程運(yùn)行開始!
子線程B運(yùn)行 : 0
A 線程運(yùn)行開始!
子線程A運(yùn)行 : 0
子線程B運(yùn)行 : 1
子線程A運(yùn)行 : 1
子線程A運(yùn)行 : 2
子線程A運(yùn)行 : 3
子線程A運(yùn)行 : 4
A 線程運(yùn)行結(jié)束!
子線程B運(yùn)行 : 2
子線程B運(yùn)行 : 3
子線程B運(yùn)行 : 4
B 線程運(yùn)行結(jié)束!
發(fā)現(xiàn)主線程比子線程早結(jié)束

加join

public class Main {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"主線程運(yùn)行開始!");
        Thread1 mTh1=new Thread1("A");
        Thread1 mTh2=new Thread1("B");
        mTh1.start();
        mTh2.start();
        try {
            mTh1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            mTh2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+ "主線程運(yùn)行結(jié)束!");

    }

}

運(yùn)行結(jié)果:
main主線程運(yùn)行開始!
A 線程運(yùn)行開始!
子線程A運(yùn)行 : 0
B 線程運(yùn)行開始!
子線程B運(yùn)行 : 0
子線程A運(yùn)行 : 1
子線程B運(yùn)行 : 1
子線程A運(yùn)行 : 2
子線程B運(yùn)行 : 2
子線程A運(yùn)行 : 3
子線程B運(yùn)行 : 3
子線程A運(yùn)行 : 4
子線程B運(yùn)行 : 4
A 線程運(yùn)行結(jié)束!
主線程一定會(huì)等子線程都結(jié)束了才結(jié)束

yield

Thread.yield()方法作用是:暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。

yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)。因此,使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實(shí)際中無(wú)法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。

結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒有效果。

class ThreadYield extends Thread{
    public ThreadYield(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println("" + this.getName() + "-----" + i);
            // 當(dāng)i為30時(shí),該線程就會(huì)把CPU時(shí)間讓掉,讓其他或者自己的線程執(zhí)行(也就是誰(shuí)先搶到誰(shuí)執(zhí)行)
            if (i ==30) {
                this.yield();
            }
        }

}
}

public class Main {

    public static void main(String[] args) {

        ThreadYield yt1 = new ThreadYield("張三");
        ThreadYield yt2 = new ThreadYield("李四");
        yt1.start();
        yt2.start();
    }

}

運(yùn)行結(jié)果:

第一種情況:李四(線程)當(dāng)執(zhí)行到30時(shí)會(huì)CPU時(shí)間讓掉,這時(shí)張三(線程)搶到CPU時(shí)間并執(zhí)行。

第二種情況:李四(線程)當(dāng)執(zhí)行到30時(shí)會(huì)CPU時(shí)間讓掉,這時(shí)李四(線程)搶到CPU時(shí)間并執(zhí)行。

sleep()和yield()的區(qū)別

sleep()和yield()的區(qū)別):sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài),所以執(zhí)行sleep()的線程在指定的時(shí)間內(nèi)肯定不會(huì)被執(zhí)行;yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。

sleep 方法使當(dāng)前運(yùn)行中的線程睡眼一段時(shí)間,進(jìn)入不可運(yùn)行狀態(tài),這段時(shí)間的長(zhǎng)短是由程序設(shè)定的,yield 方法使當(dāng)前線程讓出 CPU 占有權(quán),但讓出的時(shí)間是不可設(shè)定的。實(shí)際上,yield()方法對(duì)應(yīng)了如下操作:先檢測(cè)當(dāng)前是否有相同優(yōu)先級(jí)的線程處于同可運(yùn)行狀態(tài),如有,則把 CPU 的占有權(quán)交給此線程,否則,繼續(xù)運(yùn)行原來(lái)的線程。所以yield()方法稱為“退讓”,它把運(yùn)行機(jī)會(huì)讓給了同等優(yōu)先級(jí)的其他線程。

線程的同步

1、線程安全問題存在的原因:

由于一個(gè)線程在操作共享數(shù)據(jù)過程中,未執(zhí)行完畢的情況下,另外的線程參與進(jìn)來(lái),導(dǎo)致共享數(shù)據(jù)存在了安全問題。

2、如何解決線程安全問題

必須讓一個(gè)線程操作共享數(shù)據(jù)完畢以后,其它線程才有機(jī)會(huì)參與共享數(shù)據(jù)的操作。

3、java如何實(shí)現(xiàn)線程安全:線程的同步機(jī)制

方式一:同步代碼塊

synchronized(同步監(jiān)視器){

//需要被同步的代碼塊(即為操作共享數(shù)據(jù)的代碼)

}

1、共享數(shù)據(jù):多個(gè)線程共同操作的同一個(gè)數(shù)據(jù)(變量)

2、同步監(jiān)視器:由任何一個(gè)類的對(duì)象來(lái)充當(dāng)。哪個(gè)線程獲取此監(jiān)視器,誰(shuí)就執(zhí)行大括號(hào)里被同步的代碼。俗稱:鎖

注:在實(shí)現(xiàn)Runnable接口的方式中,考慮同步的話,可以使用this來(lái)充當(dāng)鎖。但是在繼承的方式中,慎用this

class Window2 implements Runnable {
    int ticket = 1000;// 共享數(shù)據(jù)

    public void run() {

        while (true) {
            synchronized (this) {//this表示當(dāng)前對(duì)象,本題中即為w
                if (ticket > 0) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "售票,票號(hào)為:" + ticket--);
                }
            }
        }
    }
}

public class TestWindow2 {
    public static void main(String[] args) {
        Window2 w = new Window2();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

方式二:同步方法

將操作共享數(shù)據(jù)的方法聲明為synchronized. 即此方法為同步方法,能夠保證當(dāng)其中一個(gè)線程執(zhí)行此方法時(shí),其他線程在外等待直至此線程執(zhí)行完此方法。

同步方法的鎖:this(不用顯式的寫)

class Window4 implements Runnable {
    int ticket = 1000;// 共享數(shù)據(jù)

    public void run() {
        while (true) {
            show();
        }
    }

    public synchronized void show() {
        if (ticket > 0) {
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售票,票號(hào)為:"
                    + ticket--);
        }

    }
}

public class TestWindow4 {
    public static void main(String[] args) {
        Window4 w = new Window4();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

練習(xí)

銀行有一個(gè)賬戶。有兩個(gè)儲(chǔ)戶分別向同一個(gè)賬戶存3000元,每次存1000,存3次。每次存完打印賬戶余額。

class Account{
    double balance;//余額
    public Account(){

    }
    //存錢
    public synchronized void deposit(double amt){
        notify();
        balance+=amt;
        try {
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+balance);
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Customer extends Thread{
    Account account;
    public Customer(Account account){
        this.account=account;
    }
    public void run(){
        for(int i=0;i<3;i++){
            account.deposit(1000);
        }
    }
}
public class TestAccount {
    public static void main(String[] args) {
        Account acct = new Account();
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);

        c1.setName("甲");
        c2.setName("乙");

        c1.start();
        c2.start();
    }
}

小結(jié):

釋放鎖的操作

當(dāng)前線程的同步方法、同步代碼塊執(zhí)行結(jié)束

當(dāng)前線程在同步代碼塊、同步方法中遇到break、return終止了該代碼塊、該方法的繼續(xù)執(zhí)行。

當(dāng)前線程在同步代碼塊、同步方法中出現(xiàn)了未處理的Error或Exception,導(dǎo)致異常結(jié)束

當(dāng)前線程在同步代碼塊、同步方法中執(zhí)行了線程對(duì)象的wait()方法,當(dāng)前線程暫停,并釋放鎖。

不會(huì)釋放鎖的操作

線程執(zhí)行同步代碼塊或同步方法時(shí),程序調(diào)用Thread.sleep()、Thread.yield()方法暫停當(dāng)前線程的執(zhí)行

線程的死鎖問題

死鎖:不同的線程分別占用對(duì)方需要的同步資源不放棄,都在等待對(duì)方放棄自己需要的同步資源,就形成了線程的死鎖

解決方法:專門的算法、原則;盡量減少同步資源的定義

/死鎖的問題:處理線程同步時(shí)容易出現(xiàn)。
//不同的線程分別占用對(duì)方需要的同步資源不放棄,都在等待對(duì)方放棄自己需要的同步資源,就形成了線程的死鎖
//寫代碼時(shí),要避免死鎖!
public class TestDeadLock {
    static StringBuffer sb1 = new StringBuffer();
    static StringBuffer sb2 = new StringBuffer();

    public static void main(String[] args) {
        new Thread() {
            public void run() {
                synchronized (sb1) {
                    try {
                        Thread.currentThread().sleep(10);//問題放大
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("A");
                    synchronized (sb2) {
                        sb2.append("B");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                synchronized (sb2) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("C");
                    synchronized (sb1) {
                        sb2.append("D");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();
    }

}

線程通信

wait() 與 notify() 和 notifyAll()

wait():令當(dāng)前線程掛起并放棄CPU、同步資源,使別的線程可訪問并修改共享資源,而當(dāng)前線程排隊(duì)等候再次對(duì)資源的訪問

notify():?jiǎn)拘颜谂抨?duì)等待同步資源的線程中優(yōu)先級(jí)最高者結(jié)束等待

notifyAll ():?jiǎn)拘颜谂抨?duì)等待資源的所有線程結(jié)束等待.

wait是指在一個(gè)已經(jīng)進(jìn)入了同步鎖的線程內(nèi),讓自己暫時(shí)讓出同步鎖,以便其他正在等待此鎖的線程可以得到同步鎖并運(yùn)行,只有其他線程調(diào)用了notify方法(notify并不釋放鎖,只是告訴調(diào)用過wait方法的線程可以去參與獲得鎖的競(jìng)爭(zhēng)了,但不是馬上得到鎖,因?yàn)殒i還在別人手里,別人還沒釋放),調(diào)用wait方法的一個(gè)或多個(gè)線程就會(huì)解除wait狀態(tài),重新參與競(jìng)爭(zhēng)對(duì)象鎖,程序如果可以再次得到鎖,就可以繼續(xù)向下運(yùn)行。

1)wait()、notify()和notifyAll()方法是本地方法,并且為final方法,無(wú)法被重寫。

2)當(dāng)前線程必須擁有此對(duì)象的monitor(即鎖),才能調(diào)用某個(gè)對(duì)象的wait()方法能讓當(dāng)前線程阻塞,

(這種阻塞是通過提前釋放synchronized鎖,重新去請(qǐng)求鎖導(dǎo)致的阻塞,這種請(qǐng)求必須有其他線程通過notify()或者notifyAll()喚醒重新競(jìng)爭(zhēng)獲得鎖)

3)調(diào)用某個(gè)對(duì)象的notify()方法能夠喚醒一個(gè)正在等待這個(gè)對(duì)象的monitor的線程,如果有多個(gè)線程都在等待這個(gè)對(duì)象的monitor,則只能喚醒其中一個(gè)線程;

(notify()或者notifyAll()方法并不是真正釋放鎖,必須等到synchronized方法或者語(yǔ)法塊執(zhí)行完才真正釋放鎖)

4)調(diào)用notifyAll()方法能夠喚醒所有正在等待這個(gè)對(duì)象的monitor的線程,喚醒的線程獲得鎖的概率是隨機(jī)的,取決于cpu調(diào)度

例子1(錯(cuò)誤使用導(dǎo)致線程阻塞):三個(gè)線程,線程3先擁有sum對(duì)象的鎖,然后通過sum.notify()方法通知等待sum鎖的線程去獲得鎖,但是這個(gè)時(shí)候線程1,2并沒有處于wait()導(dǎo)致的阻塞狀態(tài),而是在synchronized方法塊處阻塞了,所以,這次notify()根本沒有通知到線程1,2。然后線程3正常結(jié)束,釋放掉sum鎖,這個(gè)時(shí)候,線程1就立刻獲得了sum對(duì)象的鎖(通過synchronized獲得),然后調(diào)用sum.wait()方法釋放掉sum的鎖,線程2隨后獲得了sum對(duì)象的線程鎖(通過synchronized獲得),這個(gè)時(shí)候線程1,2都處于阻塞狀態(tài),但是悲催的是,這之后再也沒有線程主動(dòng)調(diào)用sum.notify()或者notifyAll()方法顯示喚醒這兩個(gè)線程,所以程序阻塞.

public class CyclicBarrierTest {  

    public static void main(String[] args) throws Exception {  
        final Sum sum=new Sum();  

        new Thread(new Runnable() {  
            @Override  
            public void  run() {  
                try {  
                    synchronized (sum) {  
                        System.out.println("thread3 get lock");  
                        sum.sum();  
                        sum.notifyAll(); //此時(shí)喚醒沒有作用,沒有線程等待  
                        Thread.sleep(2000);  
                        System.out.println("thread3 really release lock");  
                    }  

                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  

        new Thread(new Runnable() {  
            @Override  
            public void  run() {  
                try {  
                    synchronized (sum) {  
                        System.out.println("thread1 get lock");  
                        sum.wait();//主動(dòng)釋放掉sum對(duì)象鎖  
                        System.out.println(sum.total);  
                        System.out.println("thread1 release lock");  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  

        new Thread(new Runnable() {  
            @Override  
            public void  run() {  
                try {  
                    synchronized (sum) {  
                        System.out.println("thread2 get lock");  
                        sum.wait();  //釋放sum的對(duì)象鎖,等待其他對(duì)象喚醒(其他對(duì)象釋放sum鎖)  
                        System.out.println(sum.total);  
                        System.out.println("thread2 release lock");  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  
    }  

}  

class Sum{  
    public Integer total=0;  

    public void  sum() throws Exception{  
        total=100;  
        Thread.sleep(5000);  
    }  

}  

運(yùn)行結(jié)果:

thread3 get lock  
thread3 really release lock  
thread2 get lock  
thread1 get lock  
//程序后面一直阻塞  

例子2:還是上面程序,順序不同,把線程3放到最下面。最后線程1,2都因?yàn)闆]有再次獲得線程導(dǎo)致線程阻塞

運(yùn)行過程:

線程1先運(yùn)行獲得sum對(duì)象鎖(通過synchronized),但是隨后執(zhí)行了sum.wait()方法,主動(dòng)釋放掉了sum對(duì)象鎖,然后線程2獲得了sum對(duì)象鎖(通過synchronized),也通過sum.wait()失去sum的對(duì)象鎖,最后線程3獲得了sum對(duì)象鎖(通過synchronized),主動(dòng)通過sum.notify()通知了線程1或者2,假設(shè)是1,線程1重新通過notify()/notifyAll()的方式獲得了鎖,然后執(zhí)行完畢,隨后線程釋放鎖,然后這個(gè)時(shí)候線程2成功獲得鎖,執(zhí)行完畢。

public class CyclicBarrierTest {  

    public static void main(String[] args) throws Exception {  
        final Sum sum=new Sum();  

        new Thread(new Runnable() {  
            @Override  
            public void  run() {  
                try {  
                    synchronized (sum) {  
                        System.out.println("thread1 get lock");  
                        sum.wait();//主動(dòng)釋放sum對(duì)象鎖,等待喚醒  
                        System.out.println(sum.total);  
                        System.out.println("thread1 release lock");  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  

        new Thread(new Runnable() {  
            @Override  
            public void  run() {  
                try {  
                    synchronized (sum) {  
                        System.out.println("thread2 get lock");  
                        sum.wait();  //主動(dòng)釋放sum對(duì)象鎖,等待喚醒  
                        System.out.println(sum.total);  
                        System.out.println("thread2 release lock");  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  

        new Thread(new Runnable() {  
            @Override  
            public void  run() {  
                try {  
                    synchronized (sum) {  
                        System.out.println("thread3 get lock");  
                        sum.sum();  
                        sum.notifyAll();//喚醒其他等待線程(線程1,2)  
                        Thread.sleep(2000);  
                        System.out.println("thread3 really release lock");  
                    }  

                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  

    }  

}  

class Sum{  
    public Integer total=0;  

    public void  sum() throws Exception{  
        total=100;  
        Thread.sleep(5000);  
    }  

}  

執(zhí)行結(jié)果

thread1 get lock  
thread2 get lock  
thread3 get lock  
thread3 really release lock  
100  
thread2 release lock  
100  
thread1 release lock  

經(jīng)典例題:生產(chǎn)者/消費(fèi)者問題

生產(chǎn)者(Productor)將產(chǎn)品交給店員(Clerk),而消費(fèi)者(Customer)從店員處取走產(chǎn)品,店員一次只能持有固定數(shù)量的產(chǎn)品(比如:20),如果生產(chǎn)者試圖生產(chǎn)更多的產(chǎn)品,店員會(huì)叫生產(chǎn)者停一下,如果店中有空位放產(chǎn)品了再通知生產(chǎn)者繼續(xù)生產(chǎn);如果店中沒有產(chǎn)品了,店員會(huì)告訴消費(fèi)者等一下,如果店中有產(chǎn)品了再通知消費(fèi)者來(lái)取走產(chǎn)品。

分析:

1、是否涉及到多線程的問題?是!生產(chǎn)者、消費(fèi)者

2、是否涉及到共享數(shù)據(jù)?有!考慮線程安全問題

3、此共享數(shù)據(jù)是誰(shuí)?產(chǎn)品的數(shù)量

4、是否涉及到線程的通信呢?存在生產(chǎn)者與消費(fèi)者的通信

class Clerk{//店員
    int product;

    public synchronized void addProduct(){//生產(chǎn)產(chǎn)品
        if(product >= 20){
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            product++;
            System.out.println(Thread.currentThread().getName() + ":生產(chǎn)了第" + product + "個(gè)產(chǎn)品");
            notifyAll();
        }
    }
    public synchronized void consumeProduct(){//消費(fèi)產(chǎn)品
        if(product <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName() + ":消費(fèi)了第" + product + "個(gè)產(chǎn)品");
            product--;
            notifyAll();
        }
    }
}

class Producer implements Runnable{//生產(chǎn)者
    Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run(){
        System.out.println("生產(chǎn)者開始生產(chǎn)產(chǎn)品");
        while(true){
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            clerk.addProduct();

        }
    }
}
class Consumer implements Runnable{//消費(fèi)者
    Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run(){
        System.out.println("消費(fèi)者消費(fèi)產(chǎn)品");
        while(true){
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

public class TestProduceConsume {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p1 = new Producer(clerk);
        Consumer c1 = new Consumer(clerk);
        Thread t1 = new Thread(p1);//一個(gè)生產(chǎn)者的線程
        Thread t3 = new Thread(p1);
        Thread t2 = new Thread(c1);//一個(gè)消費(fèi)者的線程

        t1.setName("生產(chǎn)者1");
        t2.setName("消費(fèi)者1");
        t3.setName("生產(chǎn)者2");

        t1.start();
        t2.start();
        t3.start();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,207評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,603評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,813評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,110評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,305評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,532評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,033評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,268評(píng)論 2 375