多线程设计模式之六 读写锁模式

何为读写锁

呃,同样回归到问题的本质,在并发编程过程中,多个线程对共享资源的访问会导致出现线程安全问题,所以对于共享资源的访问我们一般都会选择直接加锁,例如: Synchronized, 这样整个代码块对共享资源的访问都是串行化执行,就不会存在线程安全问题。 有没有改进空间?

一般来过找到共享资源,一般都是差不多比较细粒度的,呃但是对共享资源的操作又分为 读 和 写, 在结合线程安全问题出现的原因: 对共享资源的并发修改,所以对于读操作时不存在线程安全问题,完全可以并发执行,提示效率。

读写锁互斥一览表

互斥互斥
不互斥互斥

使用 Synchronized 实现读写锁

Lock接口

public interface Lock {
    void lock();
    
    void unLock();
}

读写锁

/**
 * 我的读写锁
 * @Author: puhaiguo
 * @Date: 2022-07-11 00:22
 * @Version 1.0
 */
public class MyWriteReadLock {
    
    /*读线程的个数*/
    private int readThreadCount;
    
    /*写锁标志*/
    private boolean flag = false;
    
    /*synchronized 的拥有锁的对象*/
    private Object monitor = new Object();
    
    /*读锁*/
    public class ReadLock implements Lock{

        @Override
        public void lock() {
            synchronized (monitor){
                /*说明有读锁*/
                while (flag){
                    try {
                        monitor.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                /*走到这里说明没有读锁*/
                readThreadCount++;
            }
        }

        @Override
        public void unLock() {
            synchronized (monitor){
                readThreadCount--;
                if (readThreadCount == 0){
                    /*唤醒所有的读写线程*/
                    monitor.notifyAll();
                }
            }
        }
    }
    
    /*写锁*/
    public class WriteLock implements Lock{
        @Override
        public void lock() {
            synchronized (monitor){
                while (readThreadCount != 0 || flag){
                    try {
                        monitor.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                /*说明获取到写锁*/
                flag = true;
            }
        }

        @Override
        public void unLock() {
            synchronized (monitor){
                /*唤醒所有的读写线程*/
                monitor.notifyAll();
                flag = false;
            }
        }
    }
}

测试

public class Test {
    public static void main(String[] args) {
        MyWriteReadLock myWriteReadLock = new MyWriteReadLock();
        MyWriteReadLock.ReadLock readLock = myWriteReadLock.new ReadLock();
        MyWriteReadLock.WriteLock writeLock = myWriteReadLock.new WriteLock();
        /*20 个读线程*/
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                try {
                    readLock.lock();
                    System.out.println(Thread.currentThread().getName() + " 获取到读锁 ");
                    TimeUnit.SECONDS.sleep(2);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + " 释放读锁 ");
                    readLock.unLock();
                }

            }, "读线程" + i).start();
        }

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    writeLock.lock();
                    System.out.println(Thread.currentThread().getName() + " 获取到写锁 ");
                    TimeUnit.SECONDS.sleep(2);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + " 释放写锁 ");
                    writeLock.unLock();
                }

            }, "写线程" + i).start();
        }
    }
}

结果

在这里插入图片描述

问题

呃,读写锁的效果时实现了,但是有没有注意到出现了写锁饥饿的问题, 读锁很多的情况下,写锁一直获取不到锁

解决 思路

设计一个优化开关,比如一开始时偏向写锁,如果写锁释放锁之后又把整个开关关闭,读锁接着获取锁,如果读锁释放锁的时候再把整个开关开起来

/**
 * 我的读写锁
 * @Author: puhaiguo
 * @Date: 2022-07-11 00:22
 * @Version 1.0
 */
public class MyWriteReadLock {
    
    /*读线程的个数*/
    private int readThreadCount;
    
    /*写锁标志*/
    private boolean flag = false;
    
    /*synchronized 的拥有锁的对象*/
    private Object monitor = new Object();

    /*偏向写锁*/
    private boolean preWirte = true;
    /*写线程数量*/
    private AtomicInteger writeThreadCount = new AtomicInteger(0);
    
    /*读锁*/
    public class ReadLock implements Lock{

        @Override
        public void lock() {
            synchronized (monitor){
                /*说明有读锁*/
                //System.out.println(writeThreadCount.get());
                while (flag || (preWirte && writeThreadCount.get() != 0 ) ){
                    try {
                        monitor.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                /*走到这里说明没有读锁*/
                readThreadCount++;
            }
        }

        @Override
        public void unLock() {
            synchronized (monitor){
                readThreadCount--;
                /*设置偏向写锁*/
                preWirte = true;
                if (readThreadCount == 0){
                    /*唤醒所有的读写线程*/
                    monitor.notifyAll();
                }
            }
        }
    }
    
    /*写锁*/
    public class WriteLock implements Lock{
        @Override
        public void lock() {
            int count = writeThreadCount.addAndGet(1);/*加一*/
            System.out.println("写锁数量 " + count);
            synchronized (monitor){
                while (readThreadCount != 0 || flag){
                    try {
                        monitor.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                /*说明获取到写锁*/
                flag = true;
            }
        }

        @Override
        public void unLock() {
            synchronized (monitor){
                /*唤醒所有的读写线程*/
                monitor.notifyAll();
                flag = false;
                /*设置偏向读锁*/
                preWirte = false;
                int count = writeThreadCount.decrementAndGet();
                System.out.println("写锁数量 " + count);
            }
        }
    }
}

总结

其实关于读写线程的问题,基于AQS实现的可重入读写锁得到了很好的解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值