1. 程式人生 > >java基礎(三):多執行緒

java基礎(三):多執行緒

1.程序、執行緒

程序:正在執行的程式

執行緒:程序中負責程式執行的執行單元

即:程序的範圍>執行緒的範圍。

且:一個程序可以有多個執行緒。

2.多執行緒的意義:多部分程式碼同時執行,提高CPU使用效率
3.多執行緒的特點:CPU的隨機性

4.建立執行緒的兩種方法

(1).繼承Thread

    ^1.繼承Thread

    ^2.覆蓋run()方法

    ^3.建立執行緒物件

    ^4.呼叫start()方法開啟執行緒

(2).實現Runnable介面

    ^1.實現Runnable介面

    ^2.覆蓋run()方法

    ^3.新建Thread物件,在該物件建構函式時將上述物件作為引數傳入

    ^4.呼叫start()方法。

比較:第一種因為是繼承,java中的繼承都是單繼承,有侷限性。第二種比較靈活,且耦合也比較低,建議。

5.執行緒的狀態變化關係

執行緒的狀態有五種:被建立,執行,凍結,阻塞,消亡

    被建立->執行:start()方法。

    執行->消亡:run()方法結束。

    執行->凍結:sleep(time),wait()兩種方法。

    凍結->執行:sleep時間到了,notify()

    執行,阻塞,凍結之間的關係:

        執行:有CPU執行資格,有CPU執行權

        阻塞:有CPU執行資格,無CPU執行權

        凍結:無CPU執行資格,無CPU執行權

6.執行緒安全:當多個執行緒同時操作同一個資料時,會導致資料錯誤,造成執行緒安全問題。

(1)解決方法:(1).同步程式碼塊    (2)同步函式

    同步程式碼塊:synchronized(物件){程式碼};(可用try,catch解決錯誤)

    同步函式:正常的函式上加synchronized。

        常規函式的鎖:this

        靜態函式的鎖:類名.class(位元組碼物件)

(2)比較:同步函式一個類只有一個鎖,所以一個類一個鎖的情況用同步函式。一個類多個鎖或者多個類用一個鎖的用同步程式碼塊。

(3)加鎖為什麼能保證執行緒安全?因為加了鎖就用了單例模式。

        單例模式分成2種:餓漢式,懶漢式。

        (1)餓漢式(先建立物件,然後通過方法返回該物件)

        (2)懶漢式(先不建立物件,通過方法返回時再建立物件)

class Single{//餓漢式
    private Single single = new Single();
    Single(){}
    public Single getInstance(){
        return single;
    }
}

class Single{//懶漢式,雙重if判斷保證安全
    private Single single = null;
    Single(){}
    public Single getInstance(){
        if(single == null){
            synchronized(Single.class){
                if(single == null){
                    single = new Single();
                }
            }
        }
        return single;
    }
}

7.死鎖:加鎖時需要注意導致一些新的問題,即死鎖。

死鎖的原因:(1).同步巢狀    (2).生產者消費者的同一方(生產者轉到另一個生產者,消費者轉到另一個消費者)

(1).同步巢狀:A執行緒擁有CPU執行權,但是A內還巢狀一個B。如果A想執行B需要把執行權給B,但是B拿到執行權又進不了A,就在互相等待,造成死鎖。

synchronized(A.class){
    synchronized(B.class){
        xxx;
    }
}

(2).生產者消費者模式的幾個規則:

    ^1.生產時不可以消費(多執行緒且需要同步)

    ^2.生產一個後就不能生產,讓消費者把生產出來的東西消費掉。然後轉到生產者繼續生產。

   所以,如果一個生產者生產好了東西,然後CPU執行權轉給另外一個生產者,這個時候是不可以生產的,2個生產者互相等待別人去消費,造成死鎖。

生產者消費者模式的其他一些細節:

    ^1.判斷是生產者還是消費者時,可以用一個flag來判斷,但是不要用if(flag),用while(flag)。因為如果一個執行緒剛剛判斷完if,轉到另一個執行緒,則判斷實際是無效的。

    ^2.為了避免死鎖,一個生產者結束之後,喚醒其他執行緒的時候一定要有消費者執行緒在裡面。所以不用notify(),用notifyAll();

    ^3.但是緊接著又有一個問題,一個生產者結束後,只需要消費者,但是notifyAll()把生產者也喚醒了,降低效率,所以為了解決效率低的問題,引入一套鎖和監視器機制。

    ^4.每次只能生產一個產品,效率有點低,所以可以設定陣列,保證可以生產多個,那麼while判斷時是數量判斷。並且每次生產滿後需要將陣列的指標歸為0重新開始。

class Resource(){
    private int count;
    private boolean flag = false;//一開始預設沒有產品
    private String name = "";
    
    //生產
    public synchronized void set(String name){
        while(flag){//如果有產品了,等待
            try{
                wait();
            }catch(Exception e){
                e.printStack();
            }
        }
        count++;
        this.name = name + "..." + count; 
        system.out.println(Thread.currentThread.getName + "生產者" + this.name);
        flag = true;
        notifyAll();
    
    //消費
    public synchronized void get(){
        while(!flag){
            try{
                wait();
            }catch(Exception e){
                e.printStack();
            }
        }
        system.out.println(Thread.currentThread.getName + "消費者" + this.name);
        flag = false;
        notifyAll();
    }
}

class Producer implements Runnable{
    private Resource r;
    Producer(Resource r){
        this.r = r;
    }
    public void run(){
        while(false)
            r.set("麵包");
    }
}

class Consumer implements Runnable{
    private Resource r;
    Consumer(Resource r){
        this .r = r;
    }
    public void run(){
        while(true)
            r.get();
    }
}

public class Test{
    public static void main(String[] args){
        Resource r = new Resource();
        Producer p = new Producer(r);
        Consumer c = new Consumer(c);
        
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(c);
        Thread t4 = new Thread(c);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        
    }
}

鎖和監視器的寫法:

    private Lock lock = new ReenTrantLock();

將上述synchronized變成lock.lock();

在finally里加上lock.unlock();

    private Condition con = lock.newCondition();

    wait()變成con.await();

    notify()變成con.signal();

    notifyAll()變成con.signalAll();

在喚醒時喚醒需要的那一方即可。

 

陣列使可以同時生產多個。

    生產者:while(count == objs.length),    objs[put] = name,    if(++put == objs.length) put = 0;

    消費者:while(count == 0),    name = objs[take],    if(++take == objs.length) take = 0;

    

8.插入一個執行緒先執行

t.join():讓t執行緒先執行,執行完之後再執行原本的執行緒

9.轉化成守護執行緒

        t.setDaemon(true):垃圾回收機制是一個很典型的守護執行緒。前臺執行緒結束,守護執行緒也結束。

10.中止執行緒

        如果不是在一個執行緒,用while判斷

        如果是一個執行緒,用t.interrept();

11.優先順序

        t.setPrority(數字):數字1-5,數字越大,CPU傾向分配的資源就越多。

12.wait和sleep異同

        同:都會把執行緒從執行狀態變成凍結狀態

        異:(1).sleep時間到了會自動從凍結狀態變成執行狀態,wait不會

               (2).wait會釋放執行資格和鎖,sleep只會釋放執行資格不會釋放鎖