鍍金池/ 教程/ Java/ 多線程的實現(xiàn)方法
并發(fā)新特性—信號量 Semaphore
線程間協(xié)作:wait、notify、notifyAll
notify 通知的遺漏
notifyAll 造成的早期通知問題
多線程的實現(xiàn)方法
深入 Java 內存模型(1)
多線程環(huán)境下安全使用集合 API
并發(fā)新特性—Lock 鎖與條件變量
生產者—消費者模型
深入 Java 內存模型(2)
線程中斷
Volatile 關鍵字(上)
并發(fā)新特性—阻塞隊列與阻塞棧
可重入內置鎖
守護線程與線程阻塞
并發(fā)新特性—障礙器 CyclicBarrier
Volatile 關鍵字(下)
synchronized 關鍵字
synchronized 的另個一重要作用:內存可見性
并發(fā)新特性—Executor 框架與線程池
并發(fā)性與多線程介紹
死鎖
實現(xiàn)內存可見性的兩種方法比較:synchronized 和 Volatile
線程掛起、恢復與終止

多線程的實現(xiàn)方法

Java 中實現(xiàn)多線程有兩種方法:繼承 Thread 類、實現(xiàn) Runnable 接口,在程序開發(fā)中只要是多線程,肯定永遠以實現(xiàn) Runnable 接口為主,因為實現(xiàn) Runnable 接口相比繼承 Thread 類有如下優(yōu)勢:

  • 可以避免由于 Java 的單繼承特性而帶來的局限;
  • 增強程序的健壯性,代碼能夠被多個線程共享,代碼與數(shù)據是獨立的;
  • 適合多個相同程序代碼的線程區(qū)處理同一資源的情況。

下面以典型的買票程序(基本都是以這個為例子)為例,來說明二者的區(qū)別。

首先通過繼承 Thread 類實現(xiàn),代碼如下:

class MyThread extends Thread{  
    private int ticket = 5;  
    public void run(){  
        for (int i=0;i<10;i++)  
        {  
            if(ticket > 0){  
                System.out.println("ticket = " + ticket--);  
            }  
        }  
    }  
}  

public class ThreadDemo{  
    public static void main(String[] args){  
        new MyThread().start();  
        new MyThread().start();  
        new MyThread().start();  
    }  
} 

執(zhí)行結果如下:

http://wiki.jikexueyuan.com/project/java-concurrency/images/result.png" alt="" />

從結果中可以看出,每個線程單獨賣了 5 張票,即獨立地完成了買票的任務,但實際應用中,比如火車站售票,需要多個線程去共同完成任務,在本例中,即多個線程共同買 5 張票。

下面是通過實現(xiàn) Runnable 接口實現(xiàn)的多線程程序,代碼如下:

class MyThread implements Runnable{  
    private int ticket = 5;  
    public void run(){  
        for (int i=0;i<10;i++)  
        {  
            if(ticket > 0){  
                System.out.println("ticket = " + ticket--);  
            }  
        }  
    }  
}  

public class RunnableDemo{  
    public static void main(String[] args){  
        MyThread my = new MyThread();  
        new Thread(my).start();  
        new Thread(my).start();  
        new Thread(my).start();  
    }  
}

執(zhí)行結果如下:

http://wiki.jikexueyuan.com/project/java-concurrency/images/result1.png" alt="" />

從結果中可以看出,三個線程一共賣了 5 張票,即它們共同完成了買票的任務,實現(xiàn)了資源的共享。

針對以上代碼補充三點:

  • 在第二種方法(Runnable)中,ticket 輸出的順序并不是 54321,這是因為線程執(zhí)行的時機難以預測,ticket--并不是原子操作。
  • 在第一種方法中,我們 new 了 3 個 Thread 對象,即三個線程分別執(zhí)行三個對象中的代碼,因此便是三個線程去獨立地完成賣票的任務;而在第二種方法中,我們同樣也 new 了 3 個 Thread 對象,但只有一個 Runnable 對象,3 個 Thread 對象共享這個 Runnable 對象中的代碼,因此,便會出現(xiàn) 3 個線程共同完成賣票任務的結果。如果我們 new 出 3 個 Runnable 對象,作為參數(shù)分別傳入 3 個 Thread 對象中,那么 3 個線程便會獨立執(zhí)行各自 Runnable 對象中的代碼,即 3 個線程各自賣 5 張票。
  • 在第二種方法中,由于 3 個 Thread 對象共同執(zhí)行一個 Runnable 對象中的代碼,因此可能會造成線程的不安全,比如可能 ticket 會輸出 -1(如果我們 System.out....語句前加上線程休眠操作,該情況將很有可能出現(xiàn)),這種情況的出現(xiàn)是由于,一個線程在判斷 ticket 為 1>0 后,還沒有來得及減 1,另一個線程已經將 ticket 減 1,變?yōu)榱?0,那么接下來之前的線程再將 ticket 減 1,便得到了 -1。這就需要加入同步操作(即互斥鎖),確保同一時刻只有一個線程在執(zhí)行每次 for 循環(huán)中的操作。而在第一種方法中,并不需要加入同步操作,因為每個線程執(zhí)行自己 Thread 對象中的代碼,不存在多個線程共同執(zhí)行同一個方法的情況。