使用ReentrantLock实现读写锁

2019-09-25  本文已影响0人  周群力

问题:实现一个读写锁

:方法很多,可以用信号量,本文介绍用Monitor模型(即JDK提供的ReentrantLock)来实现读写锁。其他方案可以参考C语言实现方案

直觉上的理解
可以把执行中的读操作想象成一个一个水池里的水,如图中R部分代表水池中的水,即执行中的读操作(read)。获取读锁相当于往水池灌1平米的水,释放读锁相当于从水池放出1平米的水。

image.png

当加上写锁的时候,相当于在水池中插入一个防洪大坝。后续如果再有获取读锁的操作(往水池灌水的操作),需要判断水池里有没有大坝,有大坝则不能继续灌水


image.png

但是这里会有一个corner case:假如获取读锁的操作的代码是:

while(有大坝){
  await
}
do灌水

有可能执行完第3行、准备执行第4行的时候,另一个线程插入了大坝(获取写锁),导致执行第四行时,明明有大坝还灌水。大概就是图上这种感觉:


image.png

怎么解决这个问题呢?方案是:获取写锁时,先插入大坝,然后等待池子里的水泄洪,当水流干了,才算真正的获取到写锁。如图


image.png

实现

接口:

public interface Lock {
    void lock() throws InterruptedException;
    void unlock();
}

实现类:

public class ReadWriteLock {
    private final Condition     noWrite;
    private final AtomicInteger readCounter;
    private final AtomicInteger writeCounter;
    private final Condition     readExit;
    private final ReentrantLock rawLock;

    public ReadWriteLock() {
        ReentrantLock lock = new ReentrantLock();
        this.rawLock=lock;
        AtomicInteger readCounter = new AtomicInteger(0);
        this.readCounter = readCounter;
        AtomicInteger writeCounter = new AtomicInteger(0);
        this.writeCounter = writeCounter;
        Condition noWrite = lock.newCondition();
        this.noWrite = noWrite;
        Condition readExit = lock.newCondition();
        this.readExit = readExit;
    }

    Lock readLock() {
        return new ReadLock(noWrite, readCounter, writeCounter, readExit,rawLock);
    }

    Lock writeLock() {
        return new WriteLock(noWrite, readCounter, writeCounter, readExit,rawLock);
    }

    static class ReadLock implements Lock {

        private Condition     noWrite;
        private AtomicInteger readCounter;
        private AtomicInteger writeCounter;
        private Condition     readExit;
        private ReentrantLock rawLock;

        public ReadLock(Condition noWrite, AtomicInteger readCounter, AtomicInteger writeCounter,
                        Condition readExit, ReentrantLock rawLock) {
            this.noWrite = noWrite;
            this.readCounter = readCounter;
            this.writeCounter = writeCounter;
            this.readExit = readExit;
            this.rawLock = rawLock;
        }

        @Override
        public void lock() throws InterruptedException {
            //如果有防洪大坝了就不要再灌水了
            while (writeCounter.get() > 0) {
                noWrite.await();
            }
            //进水
            readCounter.incrementAndGet();
        }

        @Override
        public void unlock() {
            //出水
            readCounter.decrementAndGet();
            rawLock.lock();
            readExit.signalAll();
            rawLock.unlock();
        }
    }

    static class WriteLock implements Lock {

        private Condition     noWrite;
        private AtomicInteger readCounter;
        private AtomicInteger writeCounter;
        private Condition     readExit;
        private ReentrantLock rawLock;

        public WriteLock(Condition noWrite, AtomicInteger readCounter, AtomicInteger writeCounter, Condition readExit,
                         ReentrantLock rawLock) {
            this.noWrite = noWrite;
            this.readCounter = readCounter;
            this.writeCounter = writeCounter;
            this.readExit = readExit;
            this.rawLock = rawLock;
        }

        @Override
        public void lock() throws InterruptedException {
            rawLock.lock();
            //插入防洪大坝
            while(writeCounter.get()>0){
                noWrite.await();
            }
            writeCounter.incrementAndGet();
            //等待泄洪
            while (readCounter.get()>0){
                readExit.await();
            }
            //泄洪完成,此时不再有并发读和并发写
            rawLock.unlock();
        }

        @Override
        public void unlock() {
            //拔出防洪大坝
            writeCounter.decrementAndGet();
            rawLock.lock();
            //通知
            noWrite.signalAll();
            rawLock.unlock();
        }
    }

}

测试

public class ReadWriteLockTest {

    @Test
    public void multiRead() {
        ReadWriteLock lock = new ReadWriteLock();
        Lock rl = lock.readLock();
        new Thread(() -> {
            try {
                rl.lock();
                System.out.println("t1 prepare to sleep");
                Thread.sleep(2000);
                System.out.println("t1 wake up");
                rl.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                rl.lock();
                System.out.println("t2 prepare to sleep");
                Thread.sleep(2000);
                System.out.println("t2 wake up");
                rl.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Test
    public void noWriteWhenReadLock() throws InterruptedException {
        ReadWriteLock lock = new ReadWriteLock();
        Lock rl = lock.readLock();
        new Thread(() -> {
            try {
                rl.lock();
                System.out.println("t1 prepare to sleep");
                Thread.sleep(2000);
                System.out.println("t1 wake up");
                rl.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                rl.lock();
                System.out.println("t2 prepare to sleep");
                Thread.sleep(2000);
                System.out.println("t2 wake up");
                rl.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        //写锁
        Lock wl = lock.writeLock();
        new Thread(() -> {
            try {
                wl.lock();
                System.out.println("t3(write) prepare to sleep");
                Thread.sleep(2000);
                System.out.println("t3(write) wake up");
                wl.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(6000);
    }
}

上一篇下一篇

猜你喜欢

热点阅读