Java 併發:第二部分 - 多線程

在瞭解了 如何建立線程以後,咱們將會在本篇文章中瞭解能夠對多線程作什麼。
當咱們有了線程,咱們就能夠對線程作以下幾個操做:
一、讓當前線程在x毫秒的時間內睡眠;
二、等待壹個其它的線程結束;
三、管理線程的優先級,暫停壹個線程以給予壹個其它線程運行的機會;

四、中斷線程; java

咱們看看該如何去作全部這些事情。 算法

首先簡單點,咱們可讓壹個線程在指定數量的毫秒內睡眠。爲了作到這壹點,Thread類有壹個方法sleep(long millis)。可是這個方法是靜態的,因此你只能讓當前線程進入睡眠狀態。你不能選擇那個你但願它睡眠的線程,你惟壹的選擇是當前線程: shell

Thread.sleep(1000);
讓當前線程睡眠1000毫秒(也就是1秒)。可是,你必須捕獲異常,InterruptedException。若是睡眠的線程被中斷,這個異常就會發生,因此你能夠這樣作:
try {
	Thread.sleep(1000);
} catch (InterruptedException e){
	e.printStackTrace();
}
可是這不是壹個好的管理異常的方法,過壹會兒咱們將會看到如何處理這個異常。
若是你但願更加精確,你可使用sleep()方法的重載版本,帶有兩個參數毫秒和納秒的sleep()方法。這個睡眠時間的精確度依賴於系統時鐘和計時器。
舉個例子,若是你但願睡眠1000毫秒1000納秒,你能夠這樣作:
try {
	Thread.sleep(1000, 1000);
} catch (InterruptedException e){
	e.printStackTrace();
}
這裏有個小例子來測試上述代碼:
public class SleepThread {
    public static void main(String[] args) {
        System.out.println("Current time millis : " + System.currentTimeMillis());

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Current time millis : " + System.currentTimeMillis());

        System.out.println("Nano time : " + System.nanoTime());

        try {
            Thread.sleep(2, 5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Nano time : " + System.nanoTime());
    }
}
在個人電腦上,上述代碼的運行結果爲:
Current time millis : 1273959308480
Current time millis : 1273959309480
Nano time : 5878165216075
Nano time : 5878166730976

你能夠看到基於毫秒的睡眠時間很是精確,可是基於納秒的睡眠時間粗糙的多。固然,運行結果依賴於你的電腦、你的操做系統和你的配置。
另外一方面,你能夠在線程中等待着其餘線程死亡。例如,您能夠建立五個線程來計算各部分的結果,等這五個線程完成後,基於五個線程的結果計算最終的結果。這樣作,你可使用線程類的join()方法。這種方法不是靜態的,因此你能夠用在任何線程中等待它死亡。當線程在等待另外一個線程中斷時,在sleep()這種方法中會拋出InterruptedException異常。因此爲了等待線程2,你必須這樣作: 安全

try {
	thread2.join();
} catch (InterruptedException e){
	e.printStackTrace();
}


這將迫使當前線程等待 thread2 死亡。你也能夠增長超時時間,使用join(),join(long millis) 和join(long millis, int nanos)方法的重載版本,以毫秒、或者毫秒+納秒爲單位,這裏有個小例子演示了全部的用法。 多線程

public class JoinThread {
	public static void main(String[] args) {
		Thread thread2 = new Thread(new WaitRunnable());
		Thread thread3 = new Thread(new WaitRunnable());

		System.out.println("Current time millis : " + System.currentTimeMillis());

		thread2.start();

		try {
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println("Current time millis : " + System.currentTimeMillis());

		thread3.start();

		try {
			thread3.join(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println("Current time millis : " + System.currentTimeMillis());
	}

	private static class WaitRunnable implements Runnable {
		@Override
		public void run() {
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
        }
}
以上代碼在個人電腦上運行的結果以下:
Current time millis : 1274015478535
Current time millis : 1274015483538
Current time millis : 1274015484538

你能夠看到第壹個 join() 等待了另壹個線程 5 秒鐘,當咱們設置了壹個超時時,咱們只等待了壹秒鐘就從 join 方法中返回了。
當咱們使用線程時,一樣有可能更改線程的優先級。在Java虛擬機中,Thread類使用基於優先級的調度算法。因此若是壹個線程以更高的優先級進入運行態時,新的線程將會運行,而當前正在運行的線程會返回可運行態,並等待下次運行。可是這種行爲是沒法保證的,它徹底依賴於你使用的虛擬機。因此,不要依賴線程的優先級,只使用它來提升你的程序的性能。
一般來說,線程類的優先級是壹個從0到10的整數,可是有些虛擬機擁有更低或者更高的優先級。爲了瞭解優先級的範圍,你可使用線程類的常量: 併發

public class ThreadPriorityRange {
	public static void main(String[] args) {
		System.out.println("Minimal priority : " + Thread.MIN_PRIORITY);
		System.out.println("Maximal priority : " + Thread.MAX_PRIORITY);
		System.out.println("Norm priority : " + Thread.NORM_PRIORITY);
         }
}
在個人機器上,我老是獲得以下結果:
Minimal priority : 1
Maximal priority : 10
Norm priority
若是要設置線程的優先級,你可使用 Thread 類的 setPriority(int priority) 方法。若是你傳入壹個比最大優先級更大的值,這個方法就會使用最大優先級對應的值。若是你沒有指定優先級,就會默認使用當前線程的優先級。
另外壹種會用到的與優先級相關的是 yield() 方法,這個方法是靜態的,因此它做用於當前線程。這個方法的做用就是讓線程再次進入運行狀態,其它線程只能再次等待他們運行的機會。可是在實踐中,這個方法的行爲是沒法保證的。在特定系統中,它能夠實現爲壹個空操做。在實踐中想測試這個並不容易,由於它的運行結果然的是依賴於你的電腦,虛擬機和操做系統。實踐中最好不使用線程的優先級。
最後壹件你可使用線程類作的事情是中斷它。在Java中,若是壹個線程沒有結束,你沒有辦法強制它中止,它將繼續無限的執行下去。可是你可使用 Thread 類的 interrupt() 方法中斷它。這個方法中斷壹個線程,若是線程在睡覺或者正在加入另壹個線程,方法就會拋出壹個InterruptedException。你必須知道若是線程在睡覺或者加入另壹個線程,線程的中斷狀態會被清除。顧名思義,isInterrupted()方法會返回false。如下是壹個演示的小例子:
public class InterruptThread {
	public static void main(String[] args) {
		Thread thread1 = new Thread(new WaitRunnable());

		thread1.start();

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		thread1.interrupt();
	}

	private static class WaitRunnable implements Runnable {
		@Override
		public void run() {
			System.out.println("Current time millis : " + System.currentTimeMillis());

			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				System.out.println("The thread has been interrupted");
				System.out.println("The thread is interrupted : " + Thread.currentThread().isInterrupted());
			}

			System.out.println("Current time millis : " + System.currentTimeMillis());
		}
        }
}
運行後產生了這樣的結果:
Current time millis : 1274017633151
The thread has been interrupted
The thread is interrupted : false
Current time millis : 1274017634151
你能夠看到壹秒鐘以後,第二個線程被中斷,它的中斷狀態被設置爲false。若是你沒有睡眠,可是作了不少繁重的工做,你能夠像下面這樣去測試中斷,以促使你的線程正確的中斷。
public class InterruptableRunnable implements Runnable {
	@Override
	public void run() {
		while(!Thread.currentThread().isInterrupted()){
			//Heavy operation
		}
	}
}
如今你知道如何中斷壹個線程了,你能夠想象,這種簡單的捕獲InterruptedException不足以讓你的線程「安全中斷」。想象你的線程像下面這樣:
public class UglyRunnable implements Runnable {
	@Override
	public void run() {
		while(!Thread.currentThread().isInterrupted()){
			//Heavy operation
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//Other operation
		}
	}
}
如今,當你的線程在睡眠時,另外壹個線程想中斷你的線程。睡眠會被中斷,可是中斷狀態會被清除以便循環能夠繼續。建立壹個更好的線程的方法是,在捕獲InterruptedException異常以後,再次中斷線程:
public class BetterRunnable implements Runnable {
	@Override
	public void run() {
		while(!Thread.currentThread().isInterrupted()){
			//Heavy operation
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
			//Other operation
		}
	}
}

使用這段代碼,在中斷以後,中斷狀態會被恢復,循環會中止。基於你的代碼,你也能夠在中斷以後,在interrupt()方法後增長壹個 continue 語句來確保再也不操做。在某些狀況下,你還須要使用幾個if語句來檢測中斷狀態,以控制其作或者不作某些事情。
因此,咱們如今已經知道了全部用線程能夠作的事情了。我但願大家以爲這篇文章有趣。你能夠在這裏下載本文的源代碼。
下壹篇文章是關於Java併發的,咱們將會看到如何使用同步代碼來保證線程安全。 ide

本文英文原文:http://www.baptiste-wicht.com/2010/05/java-concurrency-part-2-manipulate-threads/ 性能

相關文章
相關標籤/搜索