一、暫停或停止線程的理論
在Java編程中,要暫停或停止當前正在運行的線程,有幾種方法。對於把線程轉入睡眠Sleep狀態,使用Thread.sleep()是最正確的方式。或許有人會問,為什麼不使用等待wait()或通知notify()?要知道,使用等待或通知都不是很好的方式。
線程可以使用等待wait()實現被阻塞,這屬於條件等待的方式,當條件滿足後,又會從阻塞轉為等待狀態。盡管可以在等待wait()條件那裡放一個超時設置,但等待wait()的設計目的不是這樣的,等待wait()在設計上是用於Java線程間的通信。
而使用睡眠sleep()方式,可以讓線程從當前開始睡眠指定的時間。注意不要使用睡眠sleep()方式去代替等待wait()或通知notify(),反之亦然。
等待wait()或通知notify()不應該用於暫停線程,還有一個原因,等待wait()或通知notify()需要一個鎖。只能從一個同步的方法或同步的代碼塊去調用它們,獲取鎖和釋放鎖的開銷是比較大的。而且,只是暫停線程的話,無需引入鎖機制。
sleep()與wait()還有一點不同,sleep()會把當前的線程轉入等待狀態,它不會釋放它持有的任何鎖,而wait()使得線程轉入阻塞狀態,會釋放掉自己持有的鎖。
總之,Java多線程編程並不簡單,即使是簡單的任務,如創建線程、停止線程或暫停線程,都需要認真掌握Java API。
二、暫停或停止線程的實戰
下面的例子中,要暫停線程,可以使用Thread.sleep()或TimeUnit.sleep()方法。例子中,有兩個線程,主線程由JVM啟動,它執行main()方法。第二個線程叫T1,它由主線程創建,用於循環運行游戲。我們傳遞的Runnable任務是一個無限循環,會一直運行直到我們停止它。注意看,我使用了volatile關鍵字。
主線程首先啟動T1線程,再使用stop()方法停止線程。
在這個例子中,我們有兩種方法停止線程的運行,使用Thread.sleep()方法或者是使用TimeUnit.sleep()方法。TimeUnit類既可以指定秒即TimeUnit.SECONDS,又可以指定毫秒即TimeUnit.MILLISECONDS。總的來說,使用TimeUnit的sleep()方法,使得代碼更為易讀。
import static java.lang.Thread.currentThread;
import java.util.concurrent.TimeUnit;
public class ThreadPauseDemo{
public static void main(String args[]) throws InterruptedException {
Game game = new Game();
Thread t1 = new Thread(game, "T1");
t1.start();
// 現在停止Game線程
System.out.println(currentThread().getName() + " is stopping game thread");
game.stop();
// 查看Game線程停止的狀態
TimeUnit.MILLISECONDS.sleep(200);
System.out.println(currentThread().getName() + " is finished now");
}
}
class Game implements Runnable{
private volatile boolean isStopped = false;
public void run(){
while(!isStopped){
System.out.println("Game thread is running......");
System.out.println("Game thread is now going to pause");
try{
Thread.sleep(200);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Game thread is now resumed......");
}
System.out.println("Game thread is stopped......");
}
public void stop(){
isStopped = true;
}
}
程序輸出如下:
Game thread is running......
main is stopping game thread
Game thread is now going to pause
Game thread is now resumed......
Game thread is stopped......
main is finished now
注:
volatile關鍵字:當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取這個值時,它會去主內存中讀取新值。volatile關鍵字保證了可見性。普通的共享變量不能保證可見性,因為普通共享變量被修改之後,什麼時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。
通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然後執行同步代碼,並且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。
一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼它就具備了兩層語義:
1. 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
2. 禁止進行指令重排序。
volatile關鍵字禁止指令重排序有兩層意思:
1. 當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行。
2. 在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。
volatile一般情況下不能代替sychronized,因為volatile不能保證操作的原子性,即使只是i++,實際上也是由多個原子操作組成:read i; inc; write i,假如多個線程同時執行i++,volatile只能保證他們操作的i是同一塊內存,但依然可能出現寫入髒數據的情況。
三、sleep()方法總結
Thread.sleep()方法可以讓線程暫停或停止,它還有一些細節需要注意:
1. Thread.sleep()方法是一個靜態方法,它總是可以讓當前的線程轉入睡眠。
2. 可以調用interrupt()方法把當前睡眠的線程喚醒。
3. sleep()方法不能保證線程能精准地在指定那一毫秒內轉入睡眠,它的精度取決於系統的計時器。
4. 它不會釋放它所獲得的鎖。