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》