Java 讀寫鎖的實現


一、

   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

 

         

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM