zoukankan      html  css  js  c++  java
  • 读写锁浅析

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

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

    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("细雨之中的西湖、盛开的荷花、一座断桥,淡淡几笔,足以勾勒出淡妆浓抹总相宜的杭州。
    ")
                    .append("杭州之美,在于留给旁人对美的无尽想象空间。对我们大多数人来说,杭州的美,犹如一幅盛满故事的山水画,诗意、神秘、动情;对于航天之父钱学森来说,杭州于他也是这般美丽。
    ")
                    .append("只是直到19岁,他才能借养病之机认识自己家乡的美丽,到底有点迟了。
    ")
                    .append("但钱学森一触摸到杭州这幅美卷,便充满不舍和一生的惦念。
    ")
                    .append(
                        "虽然钱学森年少时因父亲工作调动而辗转生活于北京、上海,长大后为了学业穿梭于北京和大洋彼岸的美国,但杭州,终究是钱学森生命开始的地方,这里听到过他的第一声啼哭,雕刻过他的第一个脚印——他是踏莲而生的,他先着地的双脚,让杭州望族钱家越发枝叶繁茂。
    ");
                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
    ", 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
    ", 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:人们对杭州的了解,更多地源自曾风靡一时的电视剧《新白娘子传奇》,还有鲁迅笔下那倒掉的雷峰塔。
  • 相关阅读:
    .net remoting学习(2)MarshalByRefObject与对象激活
    Android之旅Service
    Android之旅Handler与多线程
    Android之旅广播(BroadCast)
    .net remoting学习(3) 配置与服务端广播
    网址收藏
    Asp.net生命周期
    Android API 拦截系统短消息
    TCL脚本语言学习
    实现一个包含Microsoft.Advertising和SmartMad广告控件的UserControl
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/10246087.html
Copyright © 2011-2022 走看看