【Java】多線程的創建和線程安全問題

線程的狀態

準確來說,應該有五個狀態。

被創建後通過start()進入到運行狀態。執行完run()方法或者調用stop()方法,會從運行狀態進入到消亡狀態。而運行狀態,又會隨時因爲CPU的切換,進入到臨時阻塞狀態,或者叫“掛起”。如果在運行時期,調用sleep()或者wait()方法,會使線程進入凍結狀態。當休眠結束或者調用notify()方法,會讓線程回到運行狀態,或者是臨時阻塞狀態。

                                 


多線程的創建

有兩種方式,第一種是繼承Thread類,第二種是實現Runnable接口。

繼承Thread類

第一種方式繼承Thread類,將需要多線程處理的業務放到Thread類的run方法中。直接創建繼承後的類對象,使用start()方法,即可開啓多線程。

class MultiThreadDemo extends Thread{
    private String name;
    MultiThreadDemo(String name){
        this.name=name;
    }
    @Override
    public void run() {
        for(int x=0;x<100;x++){
            System.out.println("任務:"+name+"...執行第"+x+"次...Thread-name:"+Thread.currentThread().getName());
        }
    }
}

public class MulitThreadTest {
    public static void main(String[] args) {
        MultiThreadDemo mtd1=new MultiThreadDemo("gg");
        MultiThreadDemo mtd2=new MultiThreadDemo("哈哈");
        mtd1.start();
        mtd2.start();
    }
}

這種方式的弊端在於:①獲得了Thread中不需要的方法。②僅僅因爲需要開啓多線程,就繼承了Thread了,成爲了Thread類體系,而這個類本來跟Thread類毫不相干。③一旦繼承了Thread類,就不能繼承其它類了。

實現Runnable接口

第二種方式實現Runnable接口,覆寫run方法。在調用的時候,把實現了該接口的類作爲參數,傳到Thread類的構造函數中,最後調用Thread對象的start()方法即可。

class MultiThreadDemo implements Runnable{
    private String name;
    MultiThreadDemo(String name){
        this.name=name;
    }
    @Override
    public void run() {
        for(int x=0;x<100;x++){
            System.out.println("任務:"+name+"...執行第"+x+"次...Thread-name:"+Thread.currentThread().getName());
        }
    }
}

public class MulitThreadTest {
    public static void main(String[] args) {
        MultiThreadDemo mtd1=new MultiThreadDemo("哈哈");
        MultiThreadDemo mtd2=new MultiThreadDemo("gg");
        Thread t1=new Thread(mtd1);
        Thread t2=new Thread(mtd2);
        t1.start();
        t2.start();
    }
}

這種方式的優點在於:①避免了單繼承的侷限。②直接將具體任務封裝成了具體的線程對象。 


賣票示例

四個機器(線程)同時賣100張票。下面這種使用繼承Thread的方式,創建四個Ticket對象,肯定是不行的,各賣各的。

Ticket ticket1=new Ticket();
Ticket ticket2=new Ticket();
Ticket ticket3=new Ticket();
Ticket ticket4=new Ticket();
ticket1.start();
ticket2.start();
ticket3.start();
ticket4.start();

票是公有的,要被所有線程共享,一種方式是將票數變成靜態的。另外一種是實現Runnable接口,四個線程用同一個對象

class Ticket implements Runnable{
    private int num=100;
    @Override
    public void run() {
        while(true){
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
            }
        }
    }
}

public class TicektSell {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        Thread t1=new Thread(ticket);
        Thread t2=new Thread(ticket);
        Thread t3=new Thread(ticket);
        Thread t4=new Thread(ticket);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

這樣,似乎就實現了四個機器同時賣100張票。 


線程安全問題

上面的代碼,如果在if判斷裏面加上一條線程睡眠,就會出現“線程安全”問題。

if(num>0){
    try {
        Thread.sleep(100);
        }catch (InterruptedException e){e.printStackTrace();}
    System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}

票賣到了0,還在繼續賣,甚至出現了負數!

                                                                            

原因在於:假設此時票數=1,線程0進行if判斷,爲true,進入到if代碼塊。這個時候,突然CPU切換線程線程0被掛起。此時輪到線程1進行if判斷,票數還是1,依然可以進入到if代碼塊。這個時候,CPU再次切換,線程1也被掛起,輪到線程2進行if判斷,還是可以進去

接下來,CPU切回線程0,線程0將票數-1,此時票數爲0。再切回線程1,線程1繼續-1,票數變成了-1最後切回線程2,再-1,變成了-2。這樣就出現了線程安全問題。

對於線程安全問題,做法就是加鎖,我這裏沒處理完,你就不要進來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章