獨占鎖(寫鎖) / 共享鎖(讀鎖) / 互斥鎖


獨占鎖(寫鎖) / 共享鎖(讀鎖) / 互斥鎖

概念

獨占鎖:指該鎖一次只能被一個線程所持有。對ReentrantLock和Synchronized而言都是獨占鎖

共享鎖:指該鎖可以被多個線程鎖持有

對ReentrantReadWriteLock其讀鎖是共享,其寫鎖是獨占

寫的時候只能一個人寫,但是讀的時候,可以多個人同時讀

為什么會有寫鎖和讀鎖

原來我們使用ReentrantLock創建鎖的時候,是獨占鎖,也就是說一次只能一個線程訪問,但是有一個讀寫分離場景,讀的時候想同時進行,因此原來獨占鎖的並發性就沒這么好了,因為讀鎖並不會造成數據不一致的問題,因此可以多個人共享讀

多個線程 同時讀一個資源類沒有任何問題,所以為了滿足並發量,讀取共享資源應該可以同時進行,但是如果一個線程想去寫共享資源,就不應該再有其它線程可以對該資源進行讀或寫

讀-讀:能共存

讀-寫:不能共存

寫-寫:不能共存

代碼實現

實現一個讀寫緩存的操作,假設開始沒有加鎖的時候,會出現什么情況

/**
 * 讀寫鎖
 * 多個線程 同時讀一個資源類沒有任何問題,所以為了滿足並發量,讀取共享資源應該可以同時進行
 * 但是,如果一個線程想去寫共享資源,就不應該再有其它線程可以對該資源進行讀或寫
 *
 */
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * 資源類
 */
class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();
    // private Lock lock = null;

    /**
     * 定義寫操作
     * 滿足:原子 + 獨占
     * @param key
     * @param value
     */
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "\t 正在寫入:" + key);
        try {
            // 模擬網絡擁堵,延遲0.3秒
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t 寫入完成");
    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "\t 正在讀取:");
        try {
            // 模擬網絡擁堵,延遲0.3秒
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object value = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t 讀取完成:" + value);
    }


}
public class ReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache myCache = new MyCache();
        // 線程操作資源類,5個線程寫
        for (int i = 0; i < 5; i++) {
            // lambda表達式內部必須是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, String.valueOf(i)).start();
        }
        // 線程操作資源類, 5個線程讀
        for (int i = 0; i < 5; i++) {
            // lambda表達式內部必須是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

我們分別創建5個線程寫入緩存

        // 線程操作資源類,5個線程寫
        for (int i = 0; i < 5; i++) {
            // lambda表達式內部必須是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, String.valueOf(i)).start();
        }

5個線程讀取緩存,

        // 線程操作資源類, 5個線程讀
        for (int i = 0; i < 5; i++) {
            // lambda表達式內部必須是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }

最后運行結果:

0	 正在寫入:0
4	 正在寫入:4
3	 正在寫入:3
1	 正在寫入:1
2	 正在寫入:2
0	 正在讀取:
1	 正在讀取:
2	 正在讀取:
3	 正在讀取:
4	 正在讀取:
2	 寫入完成
4	 寫入完成
4	 讀取完成:null
0	 寫入完成
3	 讀取完成:null
0	 讀取完成:null
1	 寫入完成
3	 寫入完成
1	 讀取完成:null
2	 讀取完成:null

我們可以看到,在寫入的時候,寫操作都沒其它線程打斷了,這就造成了,還沒寫完,其它線程又開始寫,這樣就造成數據不一致

解決方法

上面的代碼是沒有加鎖的,這樣就會造成線程在進行寫入操作的時候,被其它線程頻繁打斷,從而不具備原子性,這個時候,我們就需要用到讀寫鎖來解決了

/**
* 創建一個讀寫鎖
* 它是一個讀寫融為一體的鎖,在使用的時候,需要轉換
*/
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

當我們在進行寫操作的時候,就需要轉換成寫鎖

// 創建一個寫鎖
rwLock.writeLock().lock();

// 寫鎖 釋放
rwLock.writeLock().unlock();

當們在進行讀操作的時候,在轉換成讀鎖

// 創建一個讀鎖
rwLock.readLock().lock();

// 讀鎖 釋放
rwLock.readLock().unlock();

這里的讀鎖和寫鎖的區別在於,寫鎖一次只能一個線程進入,執行寫操作,而讀鎖是多個線程能夠同時進入,進行讀取的操作

完整代碼:

/**
 * 讀寫鎖
 * 多個線程 同時讀一個資源類沒有任何問題,所以為了滿足並發量,讀取共享資源應該可以同時進行
 * 但是,如果一個線程想去寫共享資源,就不應該再有其它線程可以對該資源進行讀或寫
 *
 */
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 資源類
 */
class MyCache {

    /**
     * 緩存中的東西,必須保持可見性,因此使用volatile修飾
     */
    private volatile Map<String, Object> map = new HashMap<>();

    /**
     * 創建一個讀寫鎖
     * 它是一個讀寫融為一體的鎖,在使用的時候,需要轉換
     */
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    /**
     * 定義寫操作
     * 滿足:原子 + 獨占
     * @param key
     * @param value
     */
    public void put(String key, Object value) {

        // 創建一個寫鎖
        rwLock.writeLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在寫入:" + key);

            try {
                // 模擬網絡擁堵,延遲0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            map.put(key, value);

            System.out.println(Thread.currentThread().getName() + "\t 寫入完成");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 寫鎖 釋放
            rwLock.writeLock().unlock();
        }
    }

    /**
     * 獲取
     * @param key
     */
    public void get(String key) {

        // 讀鎖
        rwLock.readLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在讀取:");

            try {
                // 模擬網絡擁堵,延遲0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Object value = map.get(key);

            System.out.println(Thread.currentThread().getName() + "\t 讀取完成:" + value);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 讀鎖釋放
            rwLock.readLock().unlock();
        }
    }

    /**
     * 清空緩存
     */
    public void clean() {
        map.clear();
    }


}
public class ReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache myCache = new MyCache();

        // 線程操作資源類,5個線程寫
        for (int i = 1; i <= 5; i++) {
            // lambda表達式內部必須是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, String.valueOf(i)).start();
        }

        // 線程操作資源類, 5個線程讀
        for (int i = 1; i <= 5; i++) {
            // lambda表達式內部必須是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

運行結果:

1	 正在寫入:1
1	 寫入完成
2	 正在寫入:2
2	 寫入完成
3	 正在寫入:3
3	 寫入完成
4	 正在寫入:4
4	 寫入完成
5	 正在寫入:5
5	 寫入完成
2	 正在讀取:
3	 正在讀取:
1	 正在讀取:
4	 正在讀取:
5	 正在讀取:
2	 讀取完成:2
1	 讀取完成:1
4	 讀取完成:4
3	 讀取完成:3
5	 讀取完成:5

從運行結果我們可以看出,寫入操作是一個一個線程進行執行的,並且中間不會被打斷,而讀操作的時候,是同時5個線程進入,然后並發讀取操作


免責聲明!

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



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