線程

線程調度和執行的計時是不確定的。如果兩個線程同時運行,而且都不等待,您必須假設在任何兩個指令之間,其它線程都可以運行並修改程序變量。

 

不僅不同機器之間的結果可能不同,而且在同一機器上多次運行同一程序也可能生成不同結果。永遠不要假設一個線程會在另一個線程之前執行某些操作

 

以下示例使用兩個線程,一個用於計時,一個用於執行實際工作。主線程使用非常簡單的算法計算素數。

在它啓動之前,它創建並啓動一個計時器線程,這個線程會休眠十秒鐘,然後設置一個主線程要檢查的標誌。十秒鐘之後,主線程將停止。請注意,共享標誌被聲明成 volatile


/**
 * CalculatePrimes -- calculate as many primes as we can in ten seconds 
 */ 

public class CalculatePrimes extends Thread {

    public static final int MAX_PRIMES = 1000000;
    public static final int TEN_SECONDS = 10000;

    public volatile boolean finished = false;

    public void run() {
        int[] primes = new int[MAX_PRIMES];
        int count = 0;

        for (int i=2; count<MAX_PRIMES; i++) {

            // Check to see if the timer has expired
            if (finished) {
                break;
            }

            boolean prime = true;
            for (int j=0; j<count; j++) {
                if (i % primes[j] == 0) {
                    prime = false;
                    break;
                }
            }

            if (prime) {
                primes[count++] = i;
                System.out.println("Found prime: " + i);
            }
        }
    }

    public static void main(String[] args) {
        CalculatePrimes calculator = new CalculatePrimes();
        calculator.start();
        try {
            Thread.sleep(TEN_SECONDS);
        }
        catch (InterruptedException e) {
            // fall through
        }

        calculator.finished = true;
    }
}

線程與進程有許多共同點,不同的是線程與同一進程中的其它線程共享相同的進程上下文,包括內存。這非常便利,但也有重大責任。只要訪問共享變量(靜態或實例字段),線程就可以方便地互相交換數據,但線程還必須確保它們以受控的方式訪問共享變量,以免它們互相干擾對方的更改。 任何線程可以訪問所有其作用域內的變量,就象主線程可以訪問該變量一樣。素數示例使用了一個公用實例字段,叫做finished用於表示已經過了指定的時間。當計時器過期時,一個線程會寫這個字段;另一個線程會定期讀取這個字段,以檢查它是否應該停止。注:這個字段被聲明成 volatile,這對於這個程序的正確運行非常重要。

 

 

爲了確保可以在線程之間以受控方式共享數據,Java 語言提供了兩個關鍵字:synchronized 和 volatile。

Synchronized 有兩個重要含義:它確保了一次只有一個線程可以執行代碼的受保護部分(互斥,mutual exclusion 或者說 mutex

,而且它確保了一個線程更改的數據對於其它線程是可見的(更改的可見性)。

如果沒有同步,數據很容易就處於不一致狀態。例如,如果一個線程正在更新兩個相關值(比如,粒子的位置和速率),而另一個線程正在

讀取這兩個值,有可能在第一個線程只寫了一個值,還沒有寫另一個值的時候,調度第二個線程運行,這樣它就會看到一箇舊值和一個新值。

同步讓我們可以定義必須原子地運行的代碼塊,這樣對於其他線程而言,它們要麼都執行,要麼都不執行。

同步的原子執行或互斥方面類似於其它操作環境中的臨界段的概念。

 

Volatile比同步更簡單,只適合於控制對基本變量(整數、布爾變量等)的單個實例的訪問。當一個變量被聲明成 volatile任何

對該變量的寫操作都會繞過高速緩存,直接寫入主內存,而任何對該變量的讀取也都繞過高速緩存,直接取自主內存。這表示所有線程在任

何時候看到的 volatile 變量值都相同。

如果沒有正確的同步,線程可能會看到舊的變量值,或者引起其它形式的數據損壞。

 

 

Volatile 對於確保每個線程看到最新的變量值非常有用,但有時我們需要保護比較大的代碼片段,如涉及更新多個變量的片段。

同步使用監控器(monitor)或鎖的概念,以協調對特定代碼塊的訪問。

每個 Java 對象都有一個相關的鎖。同一時間只能有一個線程持有 Java 鎖。當線程進入 synchronized代碼塊時,線程會阻塞並等待,直到鎖可用,當它可用時,就會獲得這個鎖,然後執行代碼塊。當控制退出受保護的代碼塊時,即到達了代碼塊末尾或者拋出了沒有在 synchronized塊中捕獲的異常時,它就會釋放該鎖。

這樣,每次只有一個線程可以執行受給定監控器保護的代碼塊。從其它線程的角度看,該代碼塊可以看作是原子的,它要麼全部執行,要麼

根本不執行。

 

 

Java 鎖定合併了一種互斥形式。每次只有一個線程可以持有鎖鎖用於保護代碼塊或整個方法,必須記住是鎖的身份保護了代碼塊,而不是代碼塊本身,這一點很重要。一個鎖可以保護許多代碼塊或方法。

反之,僅僅因爲代碼塊由鎖保護並不表示兩個線程不能同時執行該代碼塊。它只表示如果兩個線程正在等待相同的鎖,則它們不能同時執行該代碼。

 

 

 

創建synchronized塊的最簡單方法是將方法聲明成 synchronized這表示在進入方法主體之前,調用者必須獲得鎖:


public class Point {
  public synchronized void setXY(int x, int y) {
    this.x = x;
    this.y = y;
  }
}
          

對於普通的 synchronized方法,這個鎖是一個對象,將針對它調用方法。對於靜態 synchronized 方法,這個鎖是與 Class 對象相關的監控器,在該對象中聲明瞭方法。

僅僅因爲 setXY() 被聲明成 synchronized 並不表示兩個不同的線程不能同時執行 setXY(),只要它們調用不同的 Point 實例的 setXY()就可同時執行。Point 實例,一次只能有一個線程執行 setXY(),或 Point 的任何其它 synchronized 方法。

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