Java Concurrency
如何使用 Thread :
-
實作 Runnable Interface :
-
繼承 Thread Class :
public class HelloRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
public class HelloThread extends Thread {
@Override
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new HelloThread()).start();
}
}
Thread 的運作方式 :
- Java 程式為從 main 方法開始執行,並依照程式碼的順序由上往下執行。
- 而 main 方法事實上就是先啟用一個 Thread 並在記憶體中規劃一個 Stack,將程式碼放入其中,把需要呼叫的 Method 依序放入 Stack 中。
- 當我們在 main 方法中 new 一個新的 Thread 並呼叫 start() 時,這個 Thread 也會在記憶體中規劃一個新的 Stack,並將 run() 的程式碼放入 Stack 中。
- Thread 的執行順序是由 CPU 控制的,故每次執行順序都不盡相同。
不同 Thread 使用相同的 Object :
-
在 Java 中,只會有一個 Heap 區,用來儲存所有被 instance 的 object,不同的 Thread 可以使用同一個 object。
public class TestThread {
class Counter {
private int c = 0;
public void set(int c) {
this.c = c;
}
public int get() {
return c;
}
}
public class HelloRunnableA implements Runnable {
@Override
public void run(Counter counter) {
int c = counter.get();
c++;
counter.set(c);
}
}
public class HelloRunnableB implements Runnable {
@Override
public void run(Counter counter) {
int c = counter.get();
c++;
counter.set(c);
}
}
public static void main(String args[]) {
Counter counter = new Counter();
// thread A
(new Thread(new HelloRunnableA(counter))).start();
// thread B
(new Thread(new HelloRunnableB(counter))).start();
}
}
此程式的預期結果 c = 2 (預期 A 先執行,執行完後再執行 B),但其結果可能不如預期,因為 Thread 的執行順序是 CPU 控制的。
可能的執行順序 :
1. Thread A: Get c
2. Thread B: Get c
3. Thread A: Increment retrieved value; result is 1.
4. Thread B: Increment retrieved value; result is 1.
5. Thread A: Set result in c; c is now 1.
6. Thread B: Set result in c; c is still 1.
解決方法 Synchronization :
-
對方法 synchronized 宣告,讓不同的 Thread 只能一個 Thread 去使用該方法,當該 Thread 使用完該方法,並離開之後,再讓下個 Thread 使用。
public class SynchronizedCounter {
private int c = 0;
public synchronized void set(int c) {
this.c = c;
}
public synchronized int get() {
return c;
}
}
與上面寫法意義相同
public class SynchronizedCounter {
private int c = 0;
public void set(int c) {
synchronized (this) {
this.c = c;
}
}
public int get() {
synchronized (this) {
return c;
}
}
}
此方法的結果與上面相同,雖然對 set() 與 get() 進行了 synchronized 宣告,但是 c++ 的動作依然是為受限制的,故答案依然是錯的。
正確的 Synchronization :
-
callers lock counter during the entire increment/decrement period :
-
caller provides atomic methods
public class HelloRunnableA implements Runnable {
@Override
public void run(Counter counter) {
synchronized(counter) {
int c = counter.get();
c++;
counter.set(c);
}
}
}
public class SynchronizedCounter {
private int c = 0;
public void synchronized increment() {
c++;
}
public void set(int c) {
this.c = c;
}
public int get() {
return c;
}
}
Thread 的生命週期 :
- 若 Thread 進到一個共用的 object 時,可以透過呼叫 wait() 來使當前的 Thread 暫時停止執行,並讓出該 object 的使用權,直到另一個 Thread 呼叫 notifyAll() 或 notify() 時,才會被喚醒,並開始等待,直到拿到該 object 的使用權時,並從當時 wait() 的程式段落開始執行。
在 while 中使用 wait() :
//pseudo code
//Queue length: 10
//Threads A, B:
// enqueue
synchronized(queue) {
while(queue.size() == 10) {
queue.wait();
}
queue.add(...);
queue.notifyAll();
}
//Threads C, D:
// dequeue
synchronized(queue) {
while (queue.size() == 0) {
queue.wait();
}
... = queue.remove();
queue.notifyAll();
}
- 上述程式碼中,條件判斷必須為一個無限迴圈,不能使用 if 去做條件判斷,由於被喚醒的 Thread 執行順序是由當時 wait() 的段落開始往下執行,故若使用 if 去做條件判斷,會使被喚醒的 Thread 直接執行 add(),進而導致程式執行錯誤。
留言
張貼留言