可重入的读写锁

  读写锁同样存在着重入问题。简单的读写锁见读写锁浅析。这里我们拿这个简单的写锁来做一个重入测试:

    @Test
    public void testNonReentrantLock()
    {
        MyReadWriteLock lock = new MyReadWriteLock();
        
        new Thread(() -> {
            {
                try
                {
                    lock.writeLock();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println("我是写锁.");
                
                // 我又来拿读锁了
                try
                {
                    lock.writeLock();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println("我还是写锁.");
                
                lock.writeUnlock();
                System.out.println("解放写锁.");
                
                lock.writeUnlock();
                System.out.println("解放写锁.");
                
            }
        }).start();
    }

  输出结果:

我是写锁.

  后续的写锁没法加了,说明并不支持重入。要支持重入,就先得认得之前加的锁,要认得之前加的锁也简单,先认得加锁所在的线程就行了。看实现:

package com.wulinfeng.test.testpilling.util;

import java.util.HashMap;
import java.util.Map;

/**
 * 可重入的读写锁
 *
 * @author wulinfeng
 * @version C10 2019年1月14日
 * @since SDP V300R003C10
 */
public class MyReentrantReadWriteLock
{
    // 读线程映射
    private Map<Thread, Integer> readThreads = new HashMap<>();
    
    // 写线程
    private Thread writeThread = null;
    
    // 写线程
    private int write = 0;
    
    // 写请求线程
    private int writeRequest = 0;
    
    public synchronized void readLock()
        throws InterruptedException
    {
        // 当前读线程
        Thread readThread = Thread.currentThread();
        
        // 加读锁
        while (!accessRead(readThread))
        {
            wait();
        }
        
        // 取到锁,重入次数自增
        readThreads.put(readThread, getReadCount(readThread) + 1);
    }
    
    public synchronized void readUnlock()
    {
        // 当前线程
        Thread currentThread = Thread.currentThread();
        
        // 是否为读线程,不是则报错
        if (!isRead(currentThread))
        {
            throw new IllegalMonitorStateException("Calling Thread does not hold a read lock on this ReadWriteLock");
        }
        
        // 获取读线程的重入次数
        int readCount = getReadCount(currentThread);
        
        // 解放读锁
        if (readCount == 1)
        {
            readThreads.remove(currentThread);
        }
        else
        {
            // 还存在重入次数则自减
            readThreads.put(currentThread, readCount - 1);
        }
        
        notifyAll();
    }
    
    public synchronized void writeLock()
        throws InterruptedException
    {
        // 写请求累加
        writeRequest++;
        
        // 获取当前写进程
        Thread currentThread = Thread.currentThread();
        
        // 加写锁
        while (!accessWrite(writeThread))
        {
            wait();
        }
        
        // 写请求自减
        writeRequest--;
        
        // 写线程自增
        write++;
        
        writeThread = currentThread;
    }
    
    public synchronized void writeUnlock()
    {
        if (!isWrite(Thread.currentThread()))
        {
            throw new IllegalMonitorStateException("Calling Thread does not hold a write lock on this ReadWriteLock");
        }
        
        // 写线程自减
        write--;
        
        // 解放写线程
        if (write == 0)
        {
            writeThread = null;
        }
        
        notifyAll();
    }
    
    /**
     * 是否允许加读锁
     *
     * @author wulinfeng
     * @param readThread
     * @return
     */
    private boolean accessRead(Thread currentThread)
    {
        // 当前线程为写线程,则允许读
        if (isWrite(currentThread))
        {
            return true;
        }
        
        // 存在写线程则不允许读
        if (hasWrite())
        {
            return false;
        }
        
        // 当前为读线程则允许读
        if (isRead(currentThread))
        {
            return true;
        }
        
        // 存在写请求则不允许读
        if (hasWriteRequest())
        {
            return false;
        }
        return true;
    }
    
    /**
     * 是否允许加写锁
     *
     * @author wulinfeng
     * @param writeThread
     * @return
     */
    private boolean accessWrite(Thread writeThread)
    {
        // 只有一个读锁,允许写
        if (isOnlyRead(writeThread))
        {
            return true;
        }
        
        // 存在读锁,不允许写
        if (hasRead())
        {
            return false;
        }
        
        // 写线程为空,允许写
        if (writeThread == null)
        {
            return true;
        }
        
        // 当前线程不为写线程(那就是读线程了),不允许写
        if (!isWrite(writeThread))
        {
            return false;
        }
        return true;
    }
    
    /**
     * 获取读线程的重入次数
     *
     * @author wulinfeng
     * @param readThread
     * @return
     */
    private int getReadCount(Thread readThread)
    {
        Integer readCount = readThreads.get(readThread);
        
        if (readCount == null)
        {
            return 0;
        }
        return readCount.intValue();
    }
    
    /**
     * 读线程不为空
     *
     * @author wulinfeng
     * @return
     */
    private boolean hasRead()
    {
        return readThreads.size() > 0;
    }
    
    /**
     * 当前线程是否读线程
     *
     * @author wulinfeng
     * @param currentThread
     * @return
     */
    private boolean isRead(Thread currentThread)
    {
        return readThreads.get(currentThread) != null;
    }
    
    /**
     * 是否只有一个读线程
     *
     * @author wulinfeng
     * @param readThread
     * @return
     */
    private boolean isOnlyRead(Thread readThread)
    {
        return readThreads.size() == 1 && readThreads.get(readThread) != null;
    }
    
    /**
     * 是否存在写线程
     *
     * @author wulinfeng
     * @return
     */
    private boolean hasWrite()
    {
        return writeThread != null;
    }
    
    /**
     * 当前线程是否为写线程
     *
     * @author wulinfeng
     * @param currentThread
     * @return
     */
    private boolean isWrite(Thread currentThread)
    {
        return writeThread == currentThread;
    }
    
    /**
     * 是否存在写请求
     *
     * @author wulinfeng
     * @return
     */
    private boolean hasWriteRequest()
    {
        return this.writeRequest > 0;
    }
}

  我们把之前的测试代码中MyReadWriteLock改为MyReentrantReadWriteLock,这次输出结果就对了:

我是写锁.
我还是写锁.
解放写锁.
解放写锁.

  最后我们测试一下实际的应用:

    @Test
    public void TestMyReentrantReadWriteLock()
    {
        MyReentrantReadWriteLock lock = new MyReentrantReadWriteLock();
        
        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
                {
                    System.out.println("开始写....");
                    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
        {
            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:人们对杭州的了解,更多地源自曾风靡一时的电视剧《新白娘子传奇》,还有鲁迅笔下那倒掉的雷峰塔。
原文地址:https://www.cnblogs.com/wuxun1997/p/10266814.html