Java線程通俗講解

Java線程介紹

通常情況下,在一個服務器上面運行的程序是很多的,可能同一時間會有多個客戶
端的程序訪問服務器,服務器都要對這些客戶端做出響應。如果我們處理器有限,
只有一個處理器的時候,如何能夠讓這些任務看起來是並行的同時執行呢?這裏就需要用到線程的知識。

當有超過一個以上執行空間時,看起來會像是有好幾件事同時發生。但實際上,只有真正的多處理器系統纔可以同時執行好幾件事。使用java多線程可以讓它看起來好像同時都在執行,也就是說,執行的動作可以在執行空間中快速切換,因此感覺上好像是每一個任務都在同時執行。在Java中,我們用Thread這個類來實現這一點。

下面我們通過代碼一起來看一下如何新建一個線程(ThreadTest)。首先,我們需要新建一個Runnable對象。稍後我們會再定義Runnable類。而這個類會定義線程會執行什麼樣的任務。之後,我們會定義Thread對象,用它來去執行Runnable定義好的任務。然後,啓動Thread,將Runnable對象的方法擺到新的執行空間中。

package web_server;

public class ThreadTest {

	public static void main(String[] args) {
		
		// 新建Runnable對象
		Runnable threadJob = new MyRunnable();
		
		// 將Runnable的實例傳給Thread的構造函數
		Thread myThread = new Thread(threadJob);
		
		// 調用start()纔會讓線程開始執行,在此之前
		// 它只是Thread一個實例,並不是真的線程
		myThread.start();
		
		System.out.println("back in main");
	}

}

接下來我們再看如何實現Runnable。Runnable是一個接口,該接口只有一個方法,就是public void run()。在run中定義要執行的方法。因爲Runnable是一個接口,線程的任務是可以被定義在任何實現Runnable的類上。線程只在乎傳入給Thread的構造函數的參數是否爲實現Runnable的類。當你把Runnable傳給Thread的構造函數時,實際上就是在給Thread取得run的辦法,這就等於你給Thread一項任務。

package web_server;

public class MyRunnable implements Runnable{
	
	// Runnable接口只有一個方法,就是public void run()
	public void run() {
		go();
	}
	
	public void go() {
		doMore();
	}
	
	public void doMore() {
		System.out.println("top o' the stack");
	}
}

線程的原子性

每一個線程變成可執行狀態之後,它就會在可執行和不可執行兩種狀態中來來回回的切換,有的時候也會出現第三種狀態,就是暫時不可執行狀態。現在假設你有多個線程在排隊等待執行,這時候哪個線程先執行,具體執行多長時間呢?這些工作都由調度器來實現。線程調度器會去決定哪個線程跑起來,而哪個線程會暫時不去執行。

但是這樣又會有一個大問題,特別是對於多線程而言,可能會發生a線程執行一段時間,然後b線程再執行一段時間。如果a線程和b線程的程序方法互不影響還好,如果a線程和b線程使用的是同樣的程序方法,可能會發生結果互相影響的問題。對於這樣的問題,我們可以通過Synchronized來解決。Synchronized關鍵字代表線程需要一把鑰匙來存取被同步化過程的線程。也就是說,通過synchronized同步化修飾過後的方法,會將方法中的內容執行完之後,再交給別的線程去執行。如果想要保護重要的數據,就把作用在數據上的方法給同步化。這部分程序,不可分割,應該被連續的執行。在古典物理學中,我們認爲原子是不可分割的最小物理單元。因此我們說synchronized修飾過的方法具有原子性。我們下面通過一個例子一起來看一下。

package web_server;

public class TestSync implements Runnable{
	private int balance = 0;
	
	public void run() {
		for(int i = 0; i < 50; i++) {
			increment();
			System.out.println("balance is " + balance);
		}
	}
	
	public void increment() {
//	public synchronized void increment() {
		int i = balance;
		balance = i + 1;
	}

}

package web_server;

public class TestSyncTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TestSync job = new TestSync();
		Thread a = new Thread(job);
		Thread b = new Thread(job);
		a.start();
		b.start();
	}

}

可以看到,使用synchronized之後執行的效果看起來很正常,而不加之後有一些怪異的值。我們這裏新建了兩個線程,這兩個線程執行的都是同樣的方法。之所以不適用synchronized會出現數字亂的問題在於,比如說執行了i=balance之後,a線程就切換成b線程去執行了。還沒有來得及累加。B線程執行一段時間之後再切換成a線程,然後a線程再繼續累加,就會出現數字錯亂的問題。而使用synchronized會讓其強制執行完累加之後再去切換線程,所以不會出現錯亂。

參考資料

《Head First Java》

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