读写锁浅析

  所谓读写锁,即是读锁和写锁的统称,它是两种锁,但放在同一个对象里,通过两个方法分别获取。适用场景是读多写少的业务,比如缓存。用法很简单,三原则:读读共享、读写互斥、写写互斥。换种说法:读锁是共享的,读锁允许其他线程的读操作,而写锁是互斥的,写锁不允许其他线程的读写操作。

  但此处有一个问题先提出来:正所谓一山不容二虎,读与写这二虎相争,必有一王。当读写并存时,我们只能取一个,取哪一个呢?正如上面说到的,读写锁使用场景是读多写少,如果一个写线程进来,而读线程很多,结果必然是写线程将苦逼的一直等待中,它会因得不到资源而产生饥渴。我们要做的,应该是保证请求写操作的线程不会被后来的读线程挤掉。看实现:

package com.wulf.test.testpilling.util;

/**
 * 实现读写锁
 *
 * @author wulf
 * @since 2019年1月10日*/
public class MyReadWriteLock
{
    // 读线程
    private int read = 0;
    
    // 写线程
    private int write = 0;
    
    // 写请求线程
    private int writeRequest = 0;
    
    /**
     * 加读锁
     *
     * @throws InterruptedException
     */
    public synchronized void readLock()
        throws InterruptedException
    {
        // 有写或者写请求线程,则让读线程等一等
        while (write > 0 || writeRequest > 0)
        {
            wait();
        }
        
        // 获取到读锁,累加
        read++;
    }
    
    /**
     * 加写锁
     *
     * @throws InterruptedException
     */
    public synchronized void writeLock()
        throws InterruptedException
    {
        // 写请求累加
        writeRequest++;
        
        // 若无写线程独占且无读线程,则写请求变成写操作
        while (write > 0 || read > 0)
        {
            wait();
        }
        
        // 到这里表示获取到了所,写请求自减
        writeRequest--;
        
        write++;
    }
    
    /**
     * 解读锁
     **/
    public synchronized void readUnlock()
    {
        read--;
        notifyAll();
    }
    
    /**
     * 解写锁
     **/
    public synchronized void writeUnlock()
    {
        write--;
        notifyAll();
    }
    
}

  这里分别用读和写的加锁、解锁方法简化了,没有使用读锁和写锁两个对象。当前如果有读线程,那么写线程会先等它读完,但不允许后面的读线程继续进来。若已存在写操作或写请求线程,后面来的读线程就会被挂起。很明显,这里通过区分出一个写请求线程来解决写线程的饥饿问题。看下测试:

package com.wulf.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.wulf.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:人们对杭州的了解,更多地源自曾风靡一时的电视剧《新白娘子传奇》,还有鲁迅笔下那倒掉的雷峰塔。

 

posted on 2019-01-09 18:16  不想下火车的人  阅读(1306)  评论(0编辑  收藏  举报

导航