Java/多線程與同步

維基教科書,自由的教學讀本

對於多線程,Java提供了豐富且強健的支持。在源代碼方面,Java提供了synchronized關鍵字,對具體一個對象實現線程獨占,完成所謂的原子操作。在程序方面,Java提供的多線程支持主要體現在Object和Thread兩個類上,可以看出,Java從開始就視每一個類實例是生存在多線程的環境當中的。

多線程[編輯]

當你用java命令行開啟一個程序的時候,java就運行在一個進程之上了。所謂進程是相對於操作系統來說的,一個程序被操作系統所啟動,就是運行了一個進程。但在一個進程內部,因為程序的需要,可以同時運行幾個線程。注意,這裡的同時的意思是,處理器按時間分配,在不同的時間段運行不同的線程,但具體一個時間點,肯定只能由一個線程存在,而具體的時間分配對於程序是無關的。與進程不同,線程享有共同的堆空間,全局空間,函數等等,但每一個線程擁有自己的棧空間。因為線程享有共同的堆空間,所以會形成在同一時間不同線程對同一個對象的操作。對於那些沒有完成的操作,可能由於別的線程的打擾而出現錯誤,所以必須實現所謂的原子操作,即在同一時間,一個操作只能由一個線程來運行,只有當該線程完成了操作以後,別的線程才可能進入。為了實現原子操作,就必須在線程將要進入該操作的時候申明對操作的控制權。

Java利用synchronized關鍵字來申明對一個對象的排他佔有,該關鍵字可以用在函數頭中,也可以在函數體的一段中聲明,具體使用如下

// 使用在函数头中
public class Processor {
    public synchronized operate(){
        // 该函数为原子操作
    }
}

以上的函數說明operate操作為一個原子操作,即每次只能由一個線程進入該函數中運行,而該類的實例為所謂的「控制權」。也就是說,線程在運行該程序的時候始終獨占該類的實例,導致別的線程無法進入。接下來的,在函數體中的申明將更好的能看清這一點。

// 使用在函数中
public class Processor {
    public operate(){
        synchronized(obj) {        
            // 该块内为原子操作
        }
    }
}

以上的函數聲明了一個程序塊為原子操作,而所謂的「控制權」聲明在括號里,即obj。也就是說,在線程進入該塊前會查看是否有別的線程佔有該obj,如果沒有,該線程即可進入該塊中運行,並且同時擁有該塊的控制權,否則,該線程將被掛起,直到在該塊中運行的線程退出該塊,該線程才能繼續進行。當線程退出該塊後,線程自動失去對obj的獨占權,別的線程才可得以進入。但是,換句話說,在synchronized申明的函數或是程序塊中,同時只能存在一個線程。下來補充說明一個特殊情況:

// 使用在函数中
public class Processor {
    public operate(){
        synchronized(obj) {
            obj = new Object();        
            // 该块内不是原子操作
        }
    }
}

在線程進入到synchronized的塊中後,obj即被替換,所以現在如有新的線程想進入該程序塊時,因為新的obj沒有被占用,所以可以進入,該程序塊失去了原子操作的功能。

Object類對多線程的支持[編輯]

與多線程相關的Object成員函數有如下一些:

  1. void notify()
  2. void notifyAll()
  3. void wait()
  4. void wait(long timeout)
  5. void wait(long timeout, int nanos)

其中,如果線程調用wait函數,則該線程則失去對該類實例的控制權,並且將自己掛起,掛起時間由輸入參數而定。比如,當一個線程運行在一個原子操作的時候,調用wait函數,就可以不用退出該程序塊而讓別的線程有機會進入該原子操作。但如果wait的時間到了,卻有別的線程還在該程序塊中運行當中,則該線程繼續掛起,直到佔有該程序塊的線程退出為止才能繼續運行。

Thread類的相關函數[編輯]