1. 程式人生 > >多執行緒中採用鎖控制併發

多執行緒中採用鎖控制併發

在多執行緒程式設計當中對於共享變數的控制非常重要,平常的程式當中由於是單執行緒去處理的,因此不會出現變數資源同時被多個執行緒同時訪問修改,程式的執行是順序的。然而多執行緒的環境中就會出現資源同時被多個執行緒獲取,同時去做修改的狀況,可以看下面一段程式:

public class MyThread implements Runnable{

	private int i=0;
	
	public void run() {
		try{
			i++;
			System.out.println(Thread.currentThread().getId() +":i is " + i);
			Thread.sleep(1000);
			
		} catch(Exception e){
			System.out.println(e.getMessage());
		} 
	}
	
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newFixedThreadPool(3);
		MyThread thread = new MyThread ();
		for(int i=0; i<6; i++){
			executorService.submit(thread);
		}
	}

}

執行這段程式後顯示的一個可能結果如下:


由執行緒池產生的ID為10,11,12的三個執行緒開始工作,在第一次的時候就同時拿到i並進行自增操作,最後同時列印輸出,因此i的值顯示均為3,後續三個執行緒分別是陸續拿到i並做修改,因此輸出的是4,5,6。

如果想對共享資源變數i的訪問採用順序的方式進行,就需要加鎖,這時大家首先想到的是用synchronized來進行鎖的控制。

public synchronized void run() {
		try{
			i++;
			System.out.println(Thread.currentThread().getId() +":i is " + i);
			Thread.sleep(1000);
			
		} catch(Exception e){
			System.out.println(e.getMessage());
		} 
	}

加鎖後執行結果:

從結果中可以看出 i 是順序加1

還有一種方式就是採用重入鎖

public class ThreadWithReentrantLock implements Runnable{

	public static ReentrantLock lock = new ReentrantLock();
	
	private int i=0;
	
	public void run() {
		try{
			lock.lock();
			i++;
			System.out.println(Thread.currentThread().getId() +":i is " + i);
			Thread.sleep(1000);
			
		} catch(Exception e){
			System.out.println(e.getMessage());
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newFixedThreadPool(3);
		ThreadWithReentrantLock thread = new ThreadWithReentrantLock();
		for(int i=0; i<6; i++){
			executorService.submit(thread);
		}
	}

}

執行結果:

無論是採用關鍵字Sychronized還是採用重入鎖都不會造成死鎖,Sychronized會有預設的計數器實現鎖的釋放,而重入鎖在finaiily程式碼塊都會將鎖釋放。重入鎖有什麼好處呢,請看下面的示例。

public class ThreadWithReentrantLock implements Runnable {

    public static ReentrantLock lock = new ReentrantLock();  
    
    private int i=0;  
      
    public void run() {
        System.out.println(Thread.currentThread().getId()+":begin..."); 
        try{
            if(lock.tryLock())
            {
                System.out.println(Thread.currentThread().getId() + ":get lock successfully...");
            } else {
                System.out.println(Thread.currentThread().getId() + ":get lock failed...");
                lock.lockInterruptibly();
            }
            
            lock.lock();  
            i++;  
            System.out.println(Thread.currentThread().getId() +":i is " + i);  
            Thread.sleep(3000);  
              
        } catch(Exception e){  
            System.out.println(e.getMessage());  
        } finally {  
            lock.unlock();  
        }  
    }  
      
    public static void main(String[] args) {  
        ExecutorService executorService = Executors.newFixedThreadPool(3);  
        ThreadWithReentrantLock  thread = new ThreadWithReentrantLock ();  
        for(int i=0; i<6; i++){
            executorService.submit(thread); 
        }  
    }  

}

執行結果:

可以看到執行緒11一直佔據著鎖,而12和10在嘗試獲取鎖的時候由於是在併發狀況下,執行緒11持有鎖,因此對他們採取了中斷,讓他們退出鎖的競爭,保證了程式的繼續進行。

從中可以得出重入鎖比Sychronized好的地方是能發現鎖是否被佔有,並且能進行中斷控制。從中可以看到,Sychronized雖然是JDK提供的同步關鍵字,但它同樣實現了鎖的功

能,本質Sychronized是一種內建鎖,是由JVM對於併發物件設定了內建鎖,保證了併發,然而內建鎖的弊病就是看不見摸不著,無法對其進行控制。開發中還是建議採用可重

入鎖,能夠顯示的控制。