讀寫鎖淺析


  所謂讀寫鎖,即是讀鎖和寫鎖的統稱,它是兩種鎖,但放在同一個對象里,通過兩個方法分別獲取。適用場景是讀多寫少的業務,比如緩存。用法很簡單,三原則:讀讀共享、讀寫互斥、寫寫互斥。換種說法:讀鎖是共享的,讀鎖允許其他線程的讀操作,而寫鎖是互斥的,寫鎖不允許其他線程的讀寫操作。

  但此處有一個問題先提出來:正所謂一山不容二虎,讀與寫這二虎相爭,必有一王。當讀寫並存時,我們只能取一個,取哪一個呢?正如上面說到的,讀寫鎖使用場景是讀多寫少,如果一個寫線程進來,而讀線程很多,結果必然是寫線程將苦逼的一直等待中,它會因得不到資源而產生飢渴。我們要做的,應該是保證請求寫操作的線程不會被后來的讀線程擠掉。看實現:

package com.wulinfeng.test.testpilling.util;

/**
 * 實現讀寫鎖
 *
 * @author wulinfeng
 * @version C10 2019年1月10日
 * @since SDP V300R003C10
 */
public class MyReadWriteLock
{
    // 讀線程
    private int read = 0;
    
    // 寫線程
    private int write = 0;
    
    // 寫請求線程
    private int writeRequest = 0;
    
    /**
     * 加讀鎖
     *
     * @author wulinfeng
     * @throws InterruptedException
     */
    public synchronized void readLock()
        throws InterruptedException
    {
        // 有寫或者寫請求線程,則讓讀線程等一等
        while (write > 0 || writeRequest > 0)
        {
            wait();
        }
        
        // 獲取到讀鎖,累加
        read++;
    }
    
    /**
     * 加寫鎖
     *
     * @author wulinfeng
     * @throws InterruptedException
     */
    public synchronized void writeLock()
        throws InterruptedException
    {
        // 寫請求累加
        writeRequest++;
        
        // 若無寫線程獨占且無讀線程,則寫請求變成寫操作
        while (write > 0 || read > 0)
        {
            wait();
        }
        
        // 到這里表示獲取到了所,寫請求自減
        writeRequest--;
        
        write++;
    }
    
    /**
     * 解讀鎖
     *
     * @author wulinfeng
     */
    public synchronized void readUnlock()
    {
        read--;
        notifyAll();
    }
    
    /**
     * 解寫鎖
     *
     * @author wulinfeng
     */
    public synchronized void writeUnlock()
    {
        write--;
        notifyAll();
    }
    
}

  這里分別用讀和寫的加鎖、解鎖方法簡化了,沒有使用讀鎖和寫鎖兩個對象。當前如果有讀線程,那么寫線程會先等它讀完,但不允許后面的讀線程繼續進來。若已存在寫操作或寫請求線程,后面來的讀線程就會被掛起。很明顯,這里通過區分出一個寫請求線程來解決寫線程的飢餓問題。看下測試:

package com.wulinfeng.test.testpilling;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.concurrent.CountDownLatch;

import org.junit.Before;
import org.junit.Test;

import com.wulinfeng.test.testpilling.util.MyReadWriteLock;

public class MyReadWriteLockTest
{
    // 文件名
    private static final String FILE_PATH = "D:\\hello.txt";
    
    // 文件
    private final File file = new File(FILE_PATH);
    
    // 讓Junit支持多線程,3個線程就先初始化3
    private CountDownLatch latch = new CountDownLatch(3);
    
    @Before
    public void createFile()
        throws IOException
    {
        // 建文件
        if (!file.exists())
        {
            System.out.println("文件不存在。");
            file.createNewFile();
            
            // 寫初始內容
            StringBuilder sb = new StringBuilder();
            sb.append("細雨之中的西湖、盛開的荷花、一座斷橋,淡淡幾筆,足以勾勒出淡妝濃抹總相宜的杭州。\n")
                .append("杭州之美,在於留給旁人對美的無盡想象空間。對我們大多數人來說,杭州的美,猶如一幅盛滿故事的山水畫,詩意、神秘、動情;對於航天之父錢學森來說,杭州於他也是這般美麗。\n")
                .append("只是直到19歲,他才能借養病之機認識自己家鄉的美麗,到底有點遲了。\n")
                .append("但錢學森一觸摸到杭州這幅美卷,便充滿不舍和一生的惦念。\n")
                .append(
                    "雖然錢學森年少時因父親工作調動而輾轉生活於北京、上海,長大后為了學業穿梭於北京和大洋彼岸的美國,但杭州,終究是錢學森生命開始的地方,這里聽到過他的第一聲啼哭,雕刻過他的第一個腳印——他是踏蓮而生的,他先着地的雙腳,讓杭州望族錢家越發枝葉繁茂。\n");
            BufferedWriter bw = null;
            
            try
            {
                bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(FILE_PATH, true), "GBK"));
                bw.write(sb.toString());
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            finally
            {
                try
                {
                    bw.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
    
    @Test
    public void TestMyReadWriteLock()
    {
        MyReadWriteLock lock = new MyReadWriteLock();
        
        // 起個線程讀
        // 讀文件
        new Thread(() -> {
            {
                BufferedReader br = null;
                
                // 加鎖
                try
                {
                    lock.readLock();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                
                // 讀文件
                try
                {
                    br = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_PATH), "GBK"));
                    int lineNo = 0;
                    String lineContent = null;
                    while ((lineContent = br.readLine()) != null)
                    {
                        System.out.printf("行號:%d:%s\n", lineNo, lineContent);
                        lineNo++;
                    }
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    try
                    {
                        br.close();
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
                
                // 解鎖
                lock.readUnlock();
            }
        }).start();
        
        latch.countDown();
        
        // 起個線程寫,寫的內容可以多一點
        new Thread(() -> {
            {
                String content = "人們對杭州的了解,更多地源自曾風靡一時的電視劇《新白娘子傳奇》,還有魯迅筆下那倒掉的雷峰塔。";
                BufferedWriter bw = null;
                
                // 加鎖
                try
                {
                    lock.writeLock();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                
                // 寫入
                try
                {
                    bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(FILE_PATH, true), "GBK"));
                    bw.write(content);
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    try
                    {
                        bw.close();
                    }
                    catch (IOException e3)
                    {
                        e3.printStackTrace();
                    }
                }
                
                // 解鎖
                lock.writeUnlock();
            }
        }).start();
                        
        latch.countDown();
        
        // 再起個線程讀,因為上面的寫線程存在,它將掛起
        new Thread(() -> {
            {
                BufferedReader br = null;
                
                // 加鎖
                try
                {
                    lock.readLock();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                
                // 讀文件
                try
                {
                    br = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_PATH), "GBK"));
                    int lineNo = 0;
                    String lineContent = null;
                    while ((lineContent = br.readLine()) != null)
                    {
                        System.out.printf("行號:%d:%s\n", lineNo, lineContent);
                        lineNo++;
                    }
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    try
                    {
                        br.close();
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
                
                // 解鎖
                lock.readUnlock();
            }
        }).start();
        
        latch.countDown();
        
        // 主線程等待
        try
        {
            latch.await();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

  先看控制台輸出:

文件不存在。
行號:0:細雨之中的西湖、盛開的荷花、一座斷橋,淡淡幾筆,足以勾勒出淡妝濃抹總相宜的杭州。
行號:1:杭州之美,在於留給旁人對美的無盡想象空間。對我們大多數人來說,杭州的美,猶如一幅盛滿故事的山水畫,詩意、神秘、動情;對於航天之父錢學森來說,杭州於他也是這般美麗。
行號:2:只是直到19歲,他才能借養病之機認識自己家鄉的美麗,到底有點遲了。
行號:3:但錢學森一觸摸到杭州這幅美卷,便充滿不舍和一生的惦念。
行號:4:雖然錢學森年少時因父親工作調動而輾轉生活於北京、上海,長大后為了學業穿梭於北京和大洋彼岸的美國,但杭州,終究是錢學森生命開始的地方,這里聽到過他的第一聲啼哭,雕刻過他的第一個腳印——他是踏蓮而生的,他先着地的雙腳,讓杭州望族錢家越發枝葉繁茂。

  再看生產的文件hello.txt,追加了我們寫入的那一行,但為啥后面的讀線程並未打印出來呢?

  因為打印到控制台太耗時,還沒來得及打印完,主線程已經跑完了。所以我們需要在回到主線程前先休眠一會兒,讓讀操作執行完:

        // 先休息一會兒
        try
        {
            Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        
        // 主線程等待
        try
        {
            latch.await();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        // 先休息一會兒
        try
        {
            Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        
        // 主線程等待
        try
        {
            latch.await();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

  這次輸出可以看到后面的讀線程也執行了:

文件不存在。
行號:0:細雨之中的西湖、盛開的荷花、一座斷橋,淡淡幾筆,足以勾勒出淡妝濃抹總相宜的杭州。
行號:1:杭州之美,在於留給旁人對美的無盡想象空間。對我們大多數人來說,杭州的美,猶如一幅盛滿故事的山水畫,詩意、神秘、動情;對於航天之父錢學森來說,杭州於他也是這般美麗。
行號:2:只是直到19歲,他才能借養病之機認識自己家鄉的美麗,到底有點遲了。
行號:3:但錢學森一觸摸到杭州這幅美卷,便充滿不舍和一生的惦念。
行號:4:雖然錢學森年少時因父親工作調動而輾轉生活於北京、上海,長大后為了學業穿梭於北京和大洋彼岸的美國,但杭州,終究是錢學森生命開始的地方,這里聽到過他的第一聲啼哭,雕刻過他的第一個腳印——他是踏蓮而生的,他先着地的雙腳,讓杭州望族錢家越發枝葉繁茂。
行號:0:細雨之中的西湖、盛開的荷花、一座斷橋,淡淡幾筆,足以勾勒出淡妝濃抹總相宜的杭州。
行號:1:杭州之美,在於留給旁人對美的無盡想象空間。對我們大多數人來說,杭州的美,猶如一幅盛滿故事的山水畫,詩意、神秘、動情;對於航天之父錢學森來說,杭州於他也是這般美麗。
行號:2:只是直到19歲,他才能借養病之機認識自己家鄉的美麗,到底有點遲了。
行號:3:但錢學森一觸摸到杭州這幅美卷,便充滿不舍和一生的惦念。
行號:4:雖然錢學森年少時因父親工作調動而輾轉生活於北京、上海,長大后為了學業穿梭於北京和大洋彼岸的美國,但杭州,終究是錢學森生命開始的地方,這里聽到過他的第一聲啼哭,雕刻過他的第一個腳印——他是踏蓮而生的,他先着地的雙腳,讓杭州望族錢家越發枝葉繁茂。
行號:5:人們對杭州的了解,更多地源自曾風靡一時的電視劇《新白娘子傳奇》,還有魯迅筆下那倒掉的雷峰塔。

 


免責聲明!

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



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