一、
synchronized和ReentrantLock的對比
到現在,看到多線程中,鎖定的方式有2種:synchronized和ReentrantLock。兩種鎖定方式各有優劣,下面簡單對比一下:
1、synchronized是關鍵字,就和if...else...一樣,是語法層面的實現,因此synchronized獲取鎖以及釋放鎖都是Java虛擬機幫助用戶完成的;ReentrantLock是類層面的實現,因此鎖的獲取以及鎖的釋放都需要用戶自己去操作。特別再次提醒,ReentrantLock在lock()完了,一定要手動unlock()
2、synchronized簡單,簡單意味着不靈活,而ReentrantLock的鎖機制給用戶的使用提供了極大的靈活性。這點在Hashtable和ConcurrentHashMap中體現得淋漓盡致。synchronized一鎖就鎖整個Hash表,而ConcurrentHashMap則利用ReentrantLock實現了鎖分離,鎖的知識segment而不是整個Hash表
3、synchronized是不公平鎖,而ReentrantLock可以指定鎖是公平的還是非公平的
4、synchronized實現等待/通知機制通知的線程是隨機的,ReentrantLock實現等待/通知機制可以有選擇性地通知
5、和synchronized相比,ReentrantLock提供給用戶多種方法用於鎖信息的獲取,比如可以知道lock是否被當前線程獲取、lock被同一個線程調用了幾次、lock是否被任意線程獲取等等
總結起來,我認為如果只需要鎖定簡單的方法、簡單的代碼塊,那么考慮使用synchronized,復雜的多線程處理場景下可以考慮使用ReentrantLock。當然這只是建議性地,還是要具體場景具體分析的。
最后,查看了很多資料,JDK1.5版本只有由於對synchronized做了諸多優化,效率上synchronized和ReentrantLock應該是差不多。
二、讀寫鎖:分為讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,這是由jvm自己控制的,你只要上好相應的鎖即可。如果你的代碼只讀數據,可以很多人同時讀,但不能同時寫,那就上讀鎖;如果你的代碼修改數據,只能有一個人在寫,且不能同時讀取,那就上寫鎖。總之,讀的時候上讀鎖,寫的時候上寫鎖!
ReentrantReadWriteLock會使用兩把鎖來解決問題,一個讀鎖,一個寫鎖
線程進入讀鎖的前提條件:
沒有其他線程的寫鎖,
沒有寫請求或者有寫請求,但調用線程和持有鎖的線程是同一個
線程進入寫鎖的前提條件:
沒有其他線程的讀鎖
沒有其他線程的寫鎖
到ReentrantReadWriteLock,首先要做的是與ReentrantLock划清界限。它和后者都是單獨的實現,彼此之間沒有繼承或實現的關系。然后就是總結這個鎖機制的特性了:
(a).重入方面其內部的WriteLock可以獲取ReadLock,但是反過來ReadLock想要獲得WriteLock則永遠都不要想。
(b).WriteLock可以降級為ReadLock,順序是:先獲得WriteLock再獲得ReadLock,然后釋放WriteLock,這時候線程將保持Readlock的持有。反過來ReadLock想要升級為WriteLock則不可能,為什么?參看(a),呵呵.
(c).ReadLock可以被多個線程持有並且在作用時排斥任何的WriteLock,而WriteLock則是完全的互斥。這一特性最為重要,因為對於高讀取頻率而相對較低寫入的數據結構,使用此類鎖同步機制則可以提高並發量。
(d).不管是ReadLock還是WriteLock都支持Interrupt,語義與ReentrantLock一致。
(e).WriteLock支持Condition並且與ReentrantLock語義一致,而ReadLock則不能使用Condition,否則拋出UnsupportedOperationException異常。
示例:讀鎖,寫鎖及讀寫鎖的緩存機制:
/** * 讀寫鎖實現 * 讀寫鎖的緩存機制 */ // 緩存的map private Map<String, Object> map = new HashMap<String, Object>(); // 讀寫鎖對象 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private String data="1"; /** * 進行讀操作 * 可以多個讀線程同時進入,寫線程不能執行 */ public String getReadWriteLock(String tt) { //獲取讀鎖,並加鎖 Lock readLock = readWriteLock.readLock(); readLock.lock(); try { System.out.println("線程名稱:"+Thread.currentThread().getName() + " be ready to read data!"); // Thread.sleep((long) (Math.random() * 3000)); Thread.sleep(3000); // this.data =tt; this.data = mainDao.getData(tt); System.out.println(Thread.currentThread().getName() + "------->>>>have read data :"+data ); return this.data; } catch (InterruptedException e) { e.printStackTrace(); } finally { //!!!!!!注意:鎖的釋放一定要在trycatch的finally中,因為如果前面程序出現異常,鎖就不能釋放了 //釋放讀鎖 readLock.unlock(); } return null; } /** * 進行寫操作 * 只能一個寫線程進入,讀線程不能執行 */ public void putReadWriteLock(String data){ //獲取寫鎖,並加鎖 Lock writeLock = readWriteLock.writeLock(); writeLock.lock(); try { System.out.println("線程名稱:"+Thread.currentThread().getName() + " be ready to write data!"); Thread.sleep(3000); this.data = data; System.out.println(Thread.currentThread().getName() + " have write data: " + data); } catch (InterruptedException e) { // e.printStackTrace(); System.out.println("error!!!"); }finally { //釋放寫鎖 writeLock.unlock(); } } /** * 設計一個緩存系統 * 讀寫鎖的應用。 * JDK1.5自帶的讀寫鎖特性,讀與讀不互斥,讀與寫互斥,寫與寫互斥。 * 為什么要使用讀寫鎖?一句話概括那就是提高系統性能,如何提高呢? * 試想,對於所有對讀的操作是不需要線程互斥的,而如果方法內 * 使用了synchronized關鍵字同步以達到線程安全,對於所有的線程不管是讀還是寫的操作都要同步。 * 這時如果有大量的讀操作時就會又性能瓶頸。 * * 所以,當一個方法內有多個線程訪問,並且方法內有讀和寫讀操作時, * 提升性能最好的線程安全辦法時采用讀寫鎖的機制對讀寫互斥、寫寫互斥。這樣對於讀讀就沒有性能問題了 * @author zhurudong * */ public void readWriteMathod(String key){ readWriteLock.readLock().lock();//讀鎖,只對寫的線程互斥 // String key = "tt"; Object value = null; try { // 嘗試從緩存中獲取數據 value = map.get(key); if (value == null) { readWriteLock.readLock().unlock();//發現目標值為null,釋放掉讀鎖 readWriteLock.writeLock().lock();//發現目標值為null,需要取值操作,上寫鎖 try { value = map.get(key);// 很嚴謹這一步。再次取目標值 if (value == null) {//很嚴謹這一步。再次判斷目標值,防止寫鎖釋放后,后面獲得寫鎖的線程再次進行取值操作 // 模擬DB操作 value = new Random().nextInt(10000) + "test"; map.put(key, value); System.out.println("db completed!"); } readWriteLock.readLock().lock();//再次對讀進行鎖住,以防止寫的操作,造成數據錯亂 } finally { /* * 先加讀鎖再釋放寫鎖讀作用: * 防止在100行出多個線程獲得寫鎖進行寫的操作,所以在寫鎖還沒有釋放前要上讀鎖 */ readWriteLock.writeLock().unlock(); } } } finally { readWriteLock.readLock().unlock(); } }
讀寫鎖與多線程驗證代碼: https://gitee.com/xdymemory00/AsyncWithLock.git