Java高並發,ReadWriteLock(讀寫鎖)


並發讀寫的時候,很容易造成數據不一致的狀態

上案例,代碼如下:

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 0; i < 5; i++) {
            final int finali= i;
            new Thread(() -> {
                try {
                    myCache.put(finali+"", finali+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

        for (int i = 0; i < 5; i++) {
            final int finali= i;
            new Thread(() -> {
                try {
                    myCache.get(finali+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    public void put(String key, Object value) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "\t-----寫入數據key");
        Thread.sleep(3000);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t-----寫入數據成功");
    }

    public void get(String key) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "\t讀取數據key");
        Thread.sleep(3000);
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t讀取數據成功" + result);
    }
}

運行結果如下:

我們可以看到的是在1進行寫入數據的時候,此時還沒有寫入成功,就已經對1進行了讀取操作,就像我們數據庫的原子性一樣,這里在還沒有對數據進行寫入完成就進行了讀取的操作,所以讀取的為空。
接下來我們看看加入了讀寫鎖的效果,這里只需要對MyCache進行修改:

加入ReadWriteLock

ReadWriteLock的作用:保證並發讀

  • 寫鎖是獨占鎖,所謂獨占即為獨自占有,別的線程既不能獲取到該鎖的寫鎖,也不能獲取到對應的讀鎖。
  • 讀鎖是共享鎖,所謂共享即是所有線程都可以共同持有該讀鎖
  • 歸納與一句換:讀讀共存 讀寫不共存 寫寫不共存

代碼如下 :

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) throws InterruptedException {
        readWriteLock.writeLock().lock();  //上寫鎖
        try {
            System.out.println(Thread.currentThread().getName() + "\t-----寫入數據key");
            Thread.sleep(3000);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t-----寫入數據成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    public void get(String key) throws InterruptedException {
        readWriteLock.readLock().lock();  //上讀鎖
        try {
            System.out.println(Thread.currentThread().getName() + "\t讀取數據key");
            Thread.sleep(3000);
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t讀取數據成功" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

運行結果如下:

 可以看到我們對寫保持了一致性,讀保證了可並發讀,防止了在寫的時候進行了讀的操作導致的不一致性,所以這就是讀寫鎖的作用 

ReadWriteLock鎖降級

鎖降級過程(當前線程):

 

 

為什么可以降級? 為什么在寫鎖釋放之前可以拿到讀鎖?

首先寫鎖是獨占的,讀鎖是共享的,然后讀寫鎖是線程間互斥的,鎖降級的前提是所有線程都希望對數據變化敏感,但是因為寫鎖只有一個,所以會發生降級。

你既然拿到寫鎖了,其他線程就沒法拿到讀鎖或者寫鎖了,你再拿讀鎖,其實不會和其他線程的寫鎖發送沖突的,因為你拿到寫鎖到寫鎖釋放這段時間其他線程是無法拿到任何鎖的。

注意以下情況不是鎖降級

如果先釋放寫鎖,再獲取讀鎖,可能在獲取之前,會有其他線程獲取到寫鎖,阻塞讀鎖的獲取,就無法感知數據變化了。所以需要先hold住寫鎖,保證數據無變化,獲取讀鎖,然后再釋放寫鎖。

其他線程在該線程釋放寫鎖之前,寫操作所做的數據更新對其他線程是不可見的。但是一旦寫鎖釋放,數據更新操作就會對其他線程可見。

思考?

即使是先釋放寫鎖,然后獲取讀鎖可能也沒有問題,只不過會可能會被其他線程的寫鎖阻塞一段時間;

但是並不意味着,隨后的這個讀操作看不到之前別的線程的寫鎖下的寫操作,只要寫鎖被釋放數據更新還是可以看到的,所以說,上述這句話“阻塞讀鎖的獲取,那么當前線程無法感知線程T的數據更新” 感覺有些瑕疵

但是需要明確的一點:用鎖降級的前提是讀優先於寫。

比如常見的查詢系統,需要保證數據的隨時可讀,如果當新線程請求讀鎖的時候,當前持有寫鎖的線程需要馬上進行降級,保證所有讀鎖的順利獲取,阻塞后續寫鎖。


免責聲明!

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



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