Java Concurrency

Java Concurrency

如何使用 Thread :

  1. 實作 Runnable Interface :

    1. public class HelloRunnable implements Runnable {
    2. @Override
    3. public void run() {
    4. System.out.println("Hello from a thread!");
    5. }
    6. public static void main(String args[]) {
    7. (new Thread(new HelloRunnable())).start();
    8. }
    9. }
  2. 繼承 Thread Class :

    1. public class HelloThread extends Thread {
    2. @Override
    3. public void run() {
    4. System.out.println("Hello from a thread!");
    5. }
    6. public static void main(String args[]) {
    7. (new HelloThread()).start();
    8. }
    9. }

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。

    1. public class TestThread {
    2. class Counter {
    3. private int c = 0;
    4. public void set(int c) {
    5. this.c = c;
    6. }
    7. public int get() {
    8. return c;
    9. }
    10. }
    11. public class HelloRunnableA implements Runnable {
    12. @Override
    13. public void run(Counter counter) {
    14. int c = counter.get();
    15. c++;
    16. counter.set(c);
    17. }
    18. }
    19. public class HelloRunnableB implements Runnable {
    20. @Override
    21. public void run(Counter counter) {
    22. int c = counter.get();
    23. c++;
    24. counter.set(c);
    25. }
    26. }
    27. public static void main(String args[]) {
    28. Counter counter = new Counter();
    29. // thread A
    30. (new Thread(new HelloRunnableA(counter))).start();
    31. // thread B
    32. (new Thread(new HelloRunnableB(counter))).start();
    33. }
    34. }
  • 此程式的預期結果 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 使用。

    1. public class SynchronizedCounter {
    2. private int c = 0;
    3. public synchronized void set(int c) {
    4. this.c = c;
    5. }
    6. public synchronized int get() {
    7. return c;
    8. }
    9. }

    與上面寫法意義相同

    1. public class SynchronizedCounter {
    2. private int c = 0;
    3. public void set(int c) {
    4. synchronized (this) {
    5. this.c = c;
    6. }
    7. }
    8. public int get() {
    9. synchronized (this) {
    10. return c;
    11. }
    12. }
    13. }
  • 此方法的結果與上面相同,雖然對 set() 與 get() 進行了 synchronized 宣告,但是 c++ 的動作依然是為受限制的,故答案依然是錯的

正確的 Synchronization :

  1. callers lock counter during the entire increment/decrement period :

    1. public class HelloRunnableA implements Runnable {
    2. @Override
    3. public void run(Counter counter) {
    4. synchronized(counter) {
    5. int c = counter.get();
    6. c++;
    7. counter.set(c);
    8. }
    9. }
    10. }
  2. caller provides atomic methods

    1. public class SynchronizedCounter {
    2. private int c = 0;
    3. public void synchronized increment() {
    4. c++;
    5. }
    6. public void set(int c) {
    7. this.c = c;
    8. }
    9. public int get() {
    10. return c;
    11. }
    12. }

Thread 的生命週期 :

  • 若 Thread 進到一個共用的 object 時,可以透過呼叫 wait() 來使當前的 Thread 暫時停止執行,並讓出該 object 的使用權,直到另一個 Thread 呼叫 notifyAll() 或 notify() 時,才會被喚醒,並開始等待,直到拿到該 object 的使用權時,並從當時 wait() 的程式段落開始執行。

在 while 中使用 wait() :

  1. //pseudo code
  2. //Queue length: 10
  3. //Threads A, B:
  4. // enqueue
  5. synchronized(queue) {
  6. while(queue.size() == 10) {
  7. queue.wait();
  8. }
  9. queue.add(...);
  10. queue.notifyAll();
  11. }
  12. //Threads C, D:
  13. // dequeue
  14. synchronized(queue) {
  15. while (queue.size() == 0) {
  16. queue.wait();
  17. }
  18. ... = queue.remove();
  19. queue.notifyAll();
  20. }
  • 上述程式碼中,條件判斷必須為一個無限迴圈,不能使用 if 去做條件判斷,由於被喚醒的 Thread 執行順序是由當時 wait() 的段落開始往下執行,故若使用 if 去做條件判斷,會使被喚醒的 Thread 直接執行 add(),進而導致程式執行錯誤。

留言