天天看點

java多線程詳解轉自:線程間通信、等待喚醒機制、生産者消費者問題(Lock,Condition)、停止線程和守護線程、線程優先級1  線程間通信2  生産者消費者問題3  停止線程和守護線程4  線程的join()方法5  線程優先級和yield()方法6  開發中什麼時候使用多線程?

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

比如一個線程給一個變量指派,而另一個線程列印這個變量。

wait():将線程等待,釋放了cpu執行權,同時将線程對象存儲到線程池中。

notify():喚醒線程池中一個等待的線程,若線程池有多個等待的線程,則任意喚醒一個。

notifyall():喚醒線程池中,所有的等待中的線程。

這三個方法都要使用在同步中,因為要對持有鎖的線程進行操作。

比如,a鎖上的線程被wait了,那麼這個線程就進入了a鎖的線程池中,隻能被a鎖的notify喚醒,而不能被不同鎖的其他線程喚醒。

是以這三個方法要使用在同步中,因為隻有同步才具有鎖。

而鎖可以是任意對象,這三個方法被鎖調用,是以這三個方法可以被任意對象調用,是以這三個方法定義在object類中。

wait()和sleep()的差別:

wait():可以指定等待的時間,也可以不指定時間,如果不指定時間,就隻能被同一個鎖的notify或notifyall喚醒。wait時線程會釋放cpu執行權,并且會釋放鎖。

sleep():必須指定線程休眠的時間,線程休眠即暫停執行。時間到了,線程就自動蘇醒,恢複運作。sleep時線程會釋放執行權,但不釋放鎖。

線程的停止:

1,如果run()方法中定義了循環,可以用循環結束标記,跳出循環,則線程就停止了。 

2,如果線程已被當機,讀不到循環結束标記,則需要通過thread類的interrupt方法中斷線程,讓線程重新獲得執行的資格,進而可以讀到循環結束标記,而結束線程。

3,setdaemon(true)方法将目前線程标記為守護線程,當運作的線程都是守護線程時,則java虛拟機退出。該方法必須在啟動線程前調用。

等待喚醒機制代碼,實作兩個線程交替執行,在控制台上交替列印兩個字元串。

等待和喚醒是同一個鎖r:

[java] 

//兩個線程交替執行,在控制台交替列印兩串字元串。  

class res{  

    string name;  

    string sex;  

    boolean flag = false; //等待喚醒機制  

}  

class input implements runnable{  

    private res r;  

    input(res r){  

        this.r = r;  

    }  

    public void run(){  

        int x = 0;  

        while(true){  

            synchronized(r){  //等待和喚醒,是同一個鎖。  

                if(r.flag)    //等待喚醒機制,true則等待,false則執行  

                    try{r.wait();}catch(exception e){}  //線程等待,進入線程池  

                if(x == 0){  

                    r.name = "luoqi";  

                    r.sex = "man";  

                }  

                else{  

                    r.name = "麗麗";  //指派時,指派了name還沒指派sex,就列印“lili----male”,加同步鎖,牢記同步前提。  

                    r.sex = "女";  

                x = (x+1)%2;  

                r.flag = true;  //等待喚醒機制  

                r.notify();  //任意喚醒線程池裡的一個被等待的線程  //等待喚醒機制  

            }  

        }  

class output implements runnable{  

    output(res r){  

                if(!r.flag)   //等待喚醒機制,false則等待,true則執行  

                system.out.println(r.name+"----"+r.sex);  

                r.flag = false;  //等待喚醒機制  

                r.notify();  //喚醒input線程  //等待喚醒機制  

class threadcommunication{  

    public static void main(string[] args){  

        res r = new res();  

        input in = new input(r);  

        output out = new output(r);  

        thread t1 = new thread(in);  

        thread t2 = new thread(out);  

        t1.start();  

        t2.start();  

以上代碼中,把兩個同步代碼塊中的代碼,封裝成兩個同步方法,一個更改兩個字段的值,另一個列印兩個字段的值。

兩個同步方法寫在res類中,這樣同步鎖都是res.class位元組碼檔案,保證了等待和喚醒是同一個鎖:

    boolean flag = false;   

    public synchronized void setres(string name,string sex){ //同步函數  

        if(this.flag)    //flag為true,則線程等待進入線程池。  

            try{this.wait();}catch(exception e){}   

        this.name = name;  //flag為false,則線程繼續執行。  

        this .sex = sex;  

        this.flag = true;  

        this.notify();   //任意喚醒線程池中一個等待的線程  

    public synchronized void getres(){  

        if(!this.flag)  //flag為false,則線程等待進入線程池。  

        system.out.println(this.name+"----"+this.sex); //flag為true則繼續執行  

        this.flag = false;  

        this.notify();  //任意喚醒線程池中一個等待的線程  

            if(x == 0)  

                r.setres("luoqi","man");  

            else  

                r.setres("麗麗","女");  

            x = (x+1)%2;      

            r.getres();  

class threadcommunication2{  

使用線程間通信和線程同步解決生産者消費者問題。

while循環判斷标記和notifyall():

當出現多個生産者和多個消費者時,必須用while循環判斷标記,和notifyall喚醒全部線程。

對于多個生産者和消費者,為什麼要定義while判斷标記?

原因:讓被喚醒的線程再一次判斷标記。

為什麼使用notifyall()?

因為需要喚醒對方線程,因為notify是随機喚醒一個線程,容易出現隻喚醒本方線程的情況,導緻程式中的所有線程都等待。

代碼和注釋:

package mypkg;  

class producerconsumerdemo{  

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

        t3.start();  

        t4.start();   

class resource{  

    private string name;  

    private int count = 1;  

    private boolean flag = false;  

    public synchronized void set(string name){  //t1  t2  

        while(this.flag)           //while循環判斷标記,讓被喚醒的線程再次判斷标記。标記為true則線程等待,為false則線程繼續執行  

            try{this.wait();} catch(exception e){}  

        this.name = name+"--"+count++;  

        system.out.println(thread.currentthread().getname()+"---生産者---"+this.name);  

        this.notifyall();   //必須喚醒對方,索性喚醒全部。因為有可能生産者喚醒了生産者,導緻有的商品被生産了但沒被消費。  

    public synchronized void get(){  //t3  t4  

        while(!this.flag)  

            try{this.wait();} catch(exception e){}            

        system.out.println(thread.currentthread().getname()+"---消費者---------"+this.name);  

        this.notifyall();  

class producer implements runnable{  

    private resource r;  

    producer(resource r){  

            r.set("+商品+");  

class consumer implements runnable{  

    consumer(resource r){  

            r.get();  

運作結果:

java多線程詳解轉自:線程間通信、等待喚醒機制、生産者消費者問題(Lock,Condition)、停止線程和守護線程、線程優先級1  線程間通信2  生産者消費者問題3  停止線程和守護線程4  線程的join()方法5  線程優先級和yield()方法6  開發中什麼時候使用多線程?

jdk1.5 中提供了線程同步和線程間通信的更新解決方案,線程同步、線程間通信和等待喚醒機制都有了變化。

1,将同步synchronized替換成顯式的lock操作。

2,将同步鎖繼承自object類的wait()、notify()、notifyall()操作,替換成了condition對象的await()、signal()、signalall()操作。

3,該condition對象可以通過顯式的lock鎖來建立。

顯式的鎖機制,以及顯式的鎖對象上的等待喚醒操作機制,同時把等待喚醒進行封裝。

封裝完後,一個鎖可以對應多個condition,等待和喚醒必須是同一個condition對象調用。

jdk1.5之前,等待和喚醒必須是同一個鎖調用;

jdk1.5之後,等待和喚醒必須是同一個condition對象調用,而一個lock鎖可以建立多個condition對象。

進而,可以在生産者線程中,隻喚醒消費者的等待線程,即調用消費者的condition對象的喚醒操作。

lock接口,它的一個子類是reentrantlock,建立對象時new一個reentrantlock對象。

reentrantlock類的常用方法:

newcondition():建立鎖lock的condition對象,用來調用操作。 

lock():擷取鎖。

unlock():釋放此鎖。

condition類的常用方法:

await(): 線程進入等待狀态,并抛出一個interruptedexception異常。

signal(): 喚醒一個等待線程。

signalall(): 喚醒所有等待線程。

import java.util.concurrent.locks.*;   

class producerconsumerdemo2{  

        thread t1 = new thread(pro);  

        thread t3 = new thread(con);  

    final lock lock = new reentrantlock();  //建立一個鎖  

    final condition condition_pro = lock.newcondition(); //建立鎖lock的condition對象,用來操作生産者線程  

    final condition condition_con = lock.newcondition(); //建立鎖lock的condition對象,用來操作消費者線程  

    public void set(string name) throws interruptedexception {  //t1  t2  

        lock.lock();  

        try{  

            while(this.flag)            

                condition_pro.await(); //await():線程等待,會抛出一個異常  

            this.name = name+"--"+count++;  

            system.out.println(thread.currentthread().getname()+"---生産者---"+this.name);  

            this.flag = true;  

            condition_con.signal();   //生産者中喚醒消費者  

        finally{  

            lock.unlock();  //釋放鎖的動作一定要執行,是以在finally中  

    public void get() throws interruptedexception {  //t3  t4  

            while(!this.flag)  

                condition_con.await();        

            system.out.println(thread.currentthread().getname()+"---消費者---------"+this.name);  

            this.flag = false;  

            condition_pro.signal();  //消費者中喚醒生産者  

            lock.unlock();  

            try{  

                r.set("+商品+");  

            catch(interruptedexception e){}  

                r.get();  

        }   

以前可以使用stop方法來停止線程,但是已經過時,那現在如何停止線程?

隻有一種方法:run方法結束。

開啟多線程運作,run方法内的運作代碼通常是循環結構,

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

特殊情況:

當線程處于了當機狀态,就不會讀取到标記,那麼線程就不會結束。

當沒有指定的方式讓當機的線程恢複到運作狀态時,這是需要對當機進行清除。

強制讓現場恢複到運作狀态中來,這樣就可以操作标記讓線程結束。

thread類中提供該方法,interrupt()方法。

interrupt()方法是把線程從當機狀态恢複到運作狀态。

thread類中的setdaemon方法

setdaemon(boolean on):

on如果為 true,則将該線程标記為守護線程。

守護線程,當正在運作的線程都是守護線程時,java 虛拟機退出。該方法必須在啟動線程前調用。

jvm退出,守護線程在背景執行,了解為背景線程;全部為背景線程時,由前台轉為背景,jvm則退出。

代碼示例:

class stopthread implements runnable{  

    private boolean flag = true;  

    public synchronized void run(){  

        while(flag){  

                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{  

        stopthread st = new stopthread();  

        thread t1 = new thread(st);  

        thread t2 = new thread(st);  

        //t1.setdaemon(true);  //守護線程,當正在運作的線程都是守護線程時,java 虛拟機退出。該方法必須在啟動線程前調用。  

        //t2.setdaemon(true);  

        int num = 0;  

            if(num++ == 60){  

                //st.changeflag();  

                t1.interrupt();  //中斷線程,讓線程從當機狀态恢複到運作狀态,這樣可以讀到flag标記進而結束線程。  

                t2.interrupt();  

                break;  //跳出while循環  

            system.out.println(thread.currentthread().getname()+"......"+num);  

        system.out.println("over");  

join():

當a線程執行到了b線程的join()方法時,那麼a線程就會等待;等b線程執行完,a才會執行。

join()可以用來臨時加入線程執行。

class demo implements runnable{  

        for(int x=0;x<70;x++){  

            system.out.println(thread.currentthread().getname()+"....."+x);  

class joindemo{  

    public static void main(string[] args) throws exception{  

        demo d = new demo();  

        thread t1 = new thread(d);  

        thread t2 = new thread(d);  

        t1.join();  //t1線程向主線程索要cpu執行權,主線程阻塞,釋放cpu執行權,但釋放後t1和t2競争cpu執行權;  

                     //t1線程執行結束後,主線程繼續。  

        for(int x=0; x<80; x++){  

            system.out.println("main...."+x);  

線程優先級:

優先級高的線程,争奪cpu執行權的頻率就高,拿到cpu資源的可能性更大,

但并不是說優先級低的線程就不執行了。

thread類中定義了三個優先級常量:

max_priority 值為10,為最高優先級;

min_priority 值為1,為最低優先級;

norm_priority 值為5,預設優先級。

建立線程将繼承建立它的父線程的優先級,父線程是指執行建立新線程的語句所線上程,它可能是主線程,也可能是另一個自定義線程。

一般情況下,主線程具有預設優先級,為5。

可以通過getpriority()方法獲得線程的優先級,也可以通過setpriority()方法來設定優先級。

yield()方法:調用該方法後,可以使具有與目前線程相同優先級的線程有運作的機會。

可以臨時暫停目前線程,釋放cpu執行權,讓相同優先級的其他線程運作。

如果沒有相同優先級的線程,那麼yield()方法什麼也不做,目前線程繼續運作。

            thread.yield(); //t1暫停,t2運作;t2暫停,t1運作。  

                             //表現為t1、t2交替執行。  

class yielddemo{  

            //system.out.println("main...."+x);  

當某些代碼需要同時被執行時,就用單獨的線程進行封裝。

比如三個for循環同時運作,用多線程,高效,代碼示例:

class threadtest{   //三個for同時運作,用多線程,高效。  

        new thread(){  //匿名内部類  

            public void run(){  

                for(int x=0; x<50; x++){  

                    system.out.println(thread.currentthread().getname()+"....."+x);  

        }.start();  

        for(int x=0; x<50; x++){  

        runnable r = new runnable(){   //匿名内部類  

                }         

        };  

        new thread(r).start();  

}