多執行緒中採用鎖控制併發
阿新 • • 發佈:2019-02-04
在多執行緒程式設計當中對於共享變數的控制非常重要,平常的程式當中由於是單執行緒去處理的,因此不會出現變數資源同時被多個執行緒同時訪問修改,程式的執行是順序的。然而多執行緒的環境中就會出現資源同時被多個執行緒獲取,同時去做修改的狀況,可以看下面一段程式:
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對於併發物件設定了內建鎖,保證了併發,然而內建鎖的弊病就是看不見摸不著,無法對其進行控制。開發中還是建議採用可重
入鎖,能夠顯示的控制。