实现一个读写锁

翻译自 Read / Write Locks in Java

对于读取操作数量明显大于写入操作的场景,使用读写锁。
先来自己实现个读写锁,之后再分析JUC包下的ReentrantReadWriteLock。
读写锁的规则:当有写操作正在运行,则读操作应该等待;当有读操作正在运行,写操作也要等待。读操作与读操作之间不会阻塞,也就是读读可以,写读,写写都不行。
这里还有个问题那就是饥饿,于是我们有加了个变量用来记录写请求,每个读操作都会先检查会不会有写请求,于是就保证了写操作优先级大于读操作,对于有大量读操作的场景可以防止饥饿。

public class ReadWriteLock {

    private int reader;
    private int write;
    private int writeRequests;

    public synchronized void lockRead() throws InterruptedException {
        while (write > 0 || writeRequests > 0) {
            wait();
        }
        reader++;
    }

    public synchronized void unlockRead() {
        reader--;
        notifyAll();
    }

    public synchronized void lockWrite() throws InterruptedException {
        writeRequests++;
        while (reader > 0 || write > 0) {
            wait();
        }
        writeRequests--;
        write++;
    }

    public synchronized void unlockWrite() {
        write--;
        notifyAll();
    }
}

但是仍有个问题没有解决,那就是死锁,上面的代码由于没有重入会造成死锁。加入重入逻辑!

重入

读读重入

    private final Map<Thread, Integer> readingThreads = new HashMap<>(); //存放的是获取读锁的线程
    private int writeAccess;  //记录获取写锁的线程个数
    private int writeRequests; //请求写锁的线程个数
    private Thread writingThread; //当前获取读锁线程

    public synchronized void lockRead() throws InterruptedException {
        Thread callingThread = Thread.currentThread();
        while (!canGetReadAccess(callingThread)) {
            wait();
        }
        readingThreads.put(callingThread, getReadAccessCount(callingThread) + 1);
    }

    public synchronized void unlockRead() {
        Thread callingThread = Thread.currentThread();
        int count = readingThreads.get(callingThread);
        if (count == 1) {
            readingThreads.remove(callingThread);
        }else {
            readingThreads.put(callingThread, count - 1);
        }
        notifyAll();
    }

    private boolean canGetReadAccess(Thread thread) {
        if (writeAccess > 0) return false;
        if (isReading(thread)) return true;
        if (writeRequests > 0) return false;
        return true;
    }

    private boolean isReading(Thread thread) {
        return readingThreads.get(thread) != null;
    }

    private int getReadAccessCount(Thread thread) {
        Integer count = readingThreads.get(thread);
        if (count == null) return 0;
        return count;
    }

我们的重入逻辑便在canGetReadAccess中,可以看出如果当前持有读锁的线程再次尝试重入,其优先级要高于写请求。这就解决了上面死循环的问题。

写写重入

    public synchronized void lockWrite() throws InterruptedException {
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while (!canGetWriteAccess(callingThread)) {
            wait();
        }
        writeRequests--;
        writeAccess++;
        writingThread = callingThread;
    }

    public synchronized void unlockWrite() {
        writeAccess--;
        if (writeAccess == 0) {
            writingThread = null;
        }
        notifyAll();
    }

    private boolean canGetWriteAccess(Thread callingThread) {
        if (hasReader()) return false;
        if (writingThread == null) return true;
        if (isWriting(callingThread)) return true;
        return false;
    }

    private boolean isWriting(Thread callingThread) {
        return callingThread == writingThread;
    }

    private boolean hasReader() {
        return readingThreads.size() > 0;
    }

二者实现不同是因为要求不同,读写锁多线程可以同时读,但是对于写来说同时只能有一个线程执行写。

读写重入
只有一个读线程的情况下,允许该线程获取写权限,因为当存在多个读线程是不允许对数据进行更改的。

    private boolean canGetWriteAccess(Thread callingThread) {
        if (isOnlyReader(callingThread)) return true; //读写重入
        if (hasReader()) return false;
        if (writingThread == null) return true;
        if (isWriting(callingThread)) return true;
        return false;
    }

    private boolean isOnlyReader(Thread callingThread) {
        return readingThreads.size() == 1 &&
                readingThreads.get(callingThread) != null;
    }

写读重入
写线程执行时,其他线程都得等待,所以并没有什么不安全

    private boolean canGetReadAccess(Thread thread) {
        if (isWriting(thread)) return true;  //写线程运行时一定只有它一个线程运行,所以并没有危险
        if (writeAccess > 0) return false;
        if (isReading(thread)) return true;  //读重入,放在写请求判断前,确保优先级比它高
        if (writeRequests > 0) return false;
        return true;
    }

完整代码

/**
 * 读重入:读重入的优先级高于写请求
 * 写重入:当前正在写的线程再次获取写权限
 * 读写重入:只有一个读线程的情况下,允许该线程获取写权限
 * 写读重入:写线程执行时,其他线程都得等待,所以并没有什么不安全
 */
public class ReadWriteLock2 {

    private final Map<Thread, Integer> readingThreads = new HashMap<>(); //存放的是获取读锁的线程
    private int writeAccess;  //记录获取写锁的线程个数
    private int writeRequests; //请求写锁的线程个数
    private Thread writingThread; //当前获取读锁线程

    public synchronized void lockRead() throws InterruptedException {
        Thread callingThread = Thread.currentThread();
        while (!canGetReadAccess(callingThread)) {
            wait();
        }
        readingThreads.put(callingThread, getReadAccessCount(callingThread) + 1);
    }

    public synchronized void unlockRead() {
        Thread callingThread = Thread.currentThread();
        if (!isReading(callingThread)) {
            throw new IllegalMonitorStateException("该线程并没有获得该实例的读锁");
        }
        int count = getReadAccessCount(callingThread);
        if (count == 1) {
            readingThreads.remove(callingThread);
        }else {
            readingThreads.put(callingThread, count - 1);
        }
        notifyAll();
    }

    private boolean canGetReadAccess(Thread thread) {
        if (isWriting(thread)) return true;  //写线程运行时一定只有它一个线程运行,所以并没有危险
        if (writeAccess > 0) return false;
        if (isReading(thread)) return true;  //读重入,放在写请求判断前,确保优先级比它高
        if (writeRequests > 0) return false;
        return true;
    }

    private boolean isReading(Thread thread) {
        return readingThreads.get(thread) != null;
    }

    private int getReadAccessCount(Thread thread) {
        Integer count = readingThreads.get(thread);
        if (count == null) return 0;
        return count;
    }

    //-----------------------写------------------------------------

    public synchronized void lockWrite() throws InterruptedException {
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while (!canGetWriteAccess(callingThread)) {
            wait();
        }
        writeRequests--;
        writeAccess++;
        writingThread = callingThread;
    }

    public synchronized void unlockWrite() {
        if (!isWriting(Thread.currentThread())) {
            throw new IllegalMonitorStateException("当前线程没有持有该对象的写锁");
        }
        writeAccess--;
        if (writeAccess == 0) {
            writingThread = null;
        }
        notifyAll();
    }

    private boolean canGetWriteAccess(Thread callingThread) {
        if (isOnlyReader(callingThread)) return true; //读写重入
        if (hasReader()) return false;
        if (writingThread == null) return true;
        if (isWriting(callingThread)) return true;
        return false;
    }

    private boolean isWriting(Thread callingThread) {
        return callingThread == writingThread;
    }

    private boolean hasReader() {
        return readingThreads.size() > 0;
    }

    private boolean isOnlyReader(Thread callingThread) {
        return readingThreads.size() == 1 &&
                readingThreads.get(callingThread) != null;
    }
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值