博客已遷移到CSDN《https://blog.csdn.net/qq_33375499》
在java中,解決同步問題,很多時候都會使用到synchronized和Lock,這兩者都是在多線程並發時候常使用的鎖機制。
synchronized是java中的一個關鍵字,也就是說是java內置的一個特性。當一個線程訪問一個被synchronized修飾的代碼塊,會自動獲取對應的一個鎖,並在執行該代碼塊時,其他線程想訪問這個代碼塊,會一直處於等待狀態,自有等該線程釋放鎖后,其他線程進行資源競爭,競爭獲取到鎖的線程才能訪問該代碼塊。
線程釋放synchronized修飾的代碼塊鎖的方式有兩種:
- 該線程執行完對應代碼塊,自動釋放鎖。
- 在執行該代碼塊是發生了異常,JVM會自動釋放鎖。
采用synchronized關鍵字來實現同步,會導致如果存在多個線程想執行該代碼塊,而當前獲取到鎖的線程又沒有釋放鎖,可想而知,其他線程只有一只等待,這將嚴重印象執行效率。Lock鎖機制的出現就是為了解決該現象。Lock是一個java接口,通過這個接口可以實現同步,使用Lock時,用戶必須手動進行鎖的釋放,否則容易出現死鎖。
ReentranLock是Lock的唯一實現類。下面簡單介紹一下ReentranLock與synchronized的區別:
- Synchronized是一個同步鎖。當一個線程A訪問synchronized修飾的代碼塊時,線程A就會獲取該代碼塊的鎖,如果這時存在其他線程范圍該代碼塊時,將會阻塞,但是不影響這些線程訪問其他非同步代碼塊。
- ReentranLock是可重入鎖。由構造方法可知,該鎖支持兩種鎖模式,公平鎖和非公平鎖。默認是非公平的。
公平鎖:當線程A獲取訪問該對象,獲取到鎖后,此時內部存在一個計數器num+1,其他線程想訪問該對象,就會進行排隊等待(等待隊列最前一個線程處於待喚醒狀態),直到線程A釋放鎖(num = 0),此時會喚醒處於待喚醒狀態的線程進行獲取鎖的操作,一直循環。如果線程A再次嘗試獲取該對象鎖是,會檢查該對象鎖釋放已經被占用,如果被占用,會做一次是否為當前線程占用鎖的判斷,如果是內部計數器num+1,並且不需要進入等待隊列,而是直接回去當前鎖。
非公平鎖:當線程A在釋放鎖后,等待對象的線程會進行資源競爭,競爭成功的線程將獲取該鎖,其他線程繼續睡眠。
公平鎖是嚴格的以FIFO的方式進行鎖的競爭,但是非公平鎖是無序的鎖競爭,剛釋放鎖的線程很大程度上能比較快的獲取到鎖,隊列中的線程只能等待,所以非公平鎖可能會有“飢餓”的問題。但是重復的鎖獲取能減小線程之間的切換,而公平鎖則是嚴格的線程切換,這樣對操作系統的影響是比較大的,所以非公平鎖的吞吐量是大於公平鎖的,這也是為什么JDK將非公平鎖作為默認的實現。
下面是接口Lock的方法:
附上對接口Lock方法的測試,有什么問題歡迎各位大佬留言評論。
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestLock { // ReentrantLock為Lock的唯一實現類 private Lock lock = new ReentrantLock(); /** * 測試使用lock 的 lock()方法 :如果鎖已經被其他線程獲取,則等待 * @param thread */ public void testLock(Thread thread){ try { // 1.獲取鎖 lock.lock(); System.out.println("線程 " + thread.getName() + " 獲取了鎖!"); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("線程 " + thread.getName() + " 釋放了鎖!"); // 必須在 finally 中釋放鎖,防止死鎖 lock.unlock(); } } /** * 測試使用lock 的 lock()方法 :通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。 * @param thread */ public void testLockInterruptibly(Thread thread){ try { // 1.獲取鎖 lock.lockInterruptibly(); System.out.println("線程 " + thread.getName() + " 獲取了鎖!"); Thread.sleep(3000); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("線程 " + thread.getName() + " 釋放了鎖!"); // 必須在 finally 中釋放鎖,防止死鎖 lock.unlock(); } } /** * 測試使用lock 的 tryLock()方法 :如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false * @param thread */ public void testTryLock(Thread thread){ if(lock.tryLock()){// 如果獲取到了鎖 try { System.out.println("線程 " + thread.getName() + " 獲取了鎖!"); Thread.sleep(3000); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("線程 " + thread.getName() + " 釋放了鎖!"); // 必須在 finally 中釋放鎖,防止死鎖 lock.unlock(); } }else { // 沒有獲取到鎖 System.out.println("線程 " + thread.getName() + " 沒有獲取到鎖!"); } } /** * 測試使用lock 的 tryLock(long time, TimeUnit unit)方法 :和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間, * 在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。 * @param thread */ public void testTryLock_time_unit(Thread thread){ try { if(lock.tryLock(1000, TimeUnit.MILLISECONDS)){// 如果獲取到了鎖 try { System.out.println("線程 " + thread.getName() + " 獲取了鎖!"); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("線程 " + thread.getName() + " 釋放了鎖!"); // 必須在 finally 中釋放鎖,防止死鎖 lock.unlock(); } }else { // 沒有獲取到鎖 System.out.println("線程 " + thread.getName() + " 沒有獲取到鎖!"); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args){ TestLock testLock = new TestLock(); Thread a = new Thread("A") { @Override public void run() { /*// 測試 lock() testLock.testLock(Thread.currentThread());*/ /*// 測試 lockInterruptibly() testLock.testLockInterruptibly(Thread.currentThread());*/ /*// 測試 tryLock() testLock.testTryLock(Thread.currentThread());*/ /*// 測試 tryLock(long time, TimeUnit unit) testLock.testTryLock_time_unit(Thread.currentThread());*/ testLock.testTryLock_time_unit(Thread.currentThread()); } }; Thread b = new Thread("B") { @Override public void run() { testLock.testTryLock(Thread.currentThread()); } }; a.start(); b.start(); } }