Java Concurrency

Java Concurrency

如何使用 Thread :

  1. 實作 Runnable Interface :

  2. 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(); 
        } 
    }
    
  3. 繼承 Thread Class :

  4. 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 :

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

  2. public class HelloRunnableA implements Runnable { 
        @Override 
        public void run(Counter counter) { 
    	    synchronized(counter) { 
    		    int c = counter.get(); 
    		    c++; 
    		    counter.set(c); 
    	    }
    	} 
    }
    
  3. caller provides atomic methods

  4. 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(),進而導致程式執行錯誤。

留言