并发编程工具 - ReentrantReadWriteLock读写分离可重入锁、项目上的使用

目录

ReentrantReadWriteLock的特点

ReentrantReadWriteLock的结构

ReentrantReadWriteLock项目上的使用


ReentrantReadWriteLock的特点

    ReentrantLock本身是一个独占锁,与优化后的synchronized在并发性能上没有太多的优势,所以需要分场景优化。基于读写分离的思想(比如mysql的一主多从,读写分离)那么ReentrantReadWriteLock就诞生了,只有读与写同时发生时需要互斥,读与读之间是安全的。那么就是AQS的独占和共享模式。

读写锁适用读多些少的场景,特点有:

1、允许多个线程同时读取共享变量

2、只允许一个线程写共享变量

3、如果一个写线程在执行写操作时,禁止读线程读取共享变量

读写锁与互斥锁的主要区别:读写锁允许多个线程同时读共享变量,而互斥锁是不允许的,这就是性能较优的关键。

读写锁与COW(Copy On Write)模式也很用于混淆:相同点都是读多写少的模式否则对性能影响较大。  不同点 读写锁:写较多时,读会进行等待,相互斥,但是没有数据一致性问题。 COW:写较多时,不会影响读的性能,但是可能出现短时间一致性的问题,以及大量的内存占用,影响GC。

 

ReentrantReadWriteLock的结构

先看看顶层接口ReadWriteLock

public interface ReadWriteLock {

    Lock readLock();

    Lock writeLock();
}

在看看实现类ReentrantReadWriteLock

public class ReentrantReadWriteLock implements ReadWriteLock {

    // 读写锁是内部类,继承自内部类Sync, AQS
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    // 读写锁类的父类Sync,AQS子类
    final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer { //省略内容 } 
}

在具体看看读写锁的,lock、unlock方法(刚好分别是AQS的独占锁和共享锁):

public class ReentrantReadWriteLock implements ReadWriteLock {

    // 写锁
    public static class WriteLock implements Lock {
        private final Sync sync;

        public void lock() {
            sync.acquire(1);       // AQS独占锁的获取方法
        }

        public void unlock() {
            sync.release(1);       // AQS独占锁的释放方法
        }
    }
    
    // 读锁
    public static class ReadLock implements Lock {
        private final Sync sync;
        
        public void lock() {
            sync.acquireShared(1); // AQS共享锁的获取方法
        }
        
        public void unlock() {
            sync.releaseShared(1); // AQS共享锁的释放方法
        }
    }
}

 

 

ReentrantReadWriteLock项目上的使用

    背景:项目上的使用,数据本身不多,并且修改也是基本不会发生(参考上面的读写锁特点,以及与COW的特点对比)。但是为了防止数据改变之后,还需要重启等,所以还是可以运行定时线程执行重新查询全量数据操作,所以使用了读写锁。当前因为数据半年不会变动,所以使用Spring Boot的CommandLineRunner回调加载实现(回调的执行时机可以参见:Spring Boot源码(二) - SpringApplication的run方法)。但是如果后续需要定时任务更新缓存,则可以参见使用定时任务线程执行,如下:

@Slf4j
@Repository
public class MdmAgencyCache implements InitializingBean {

    public static final ScheduledExecutorService SINGLE_POOL = Executors.newSingleThreadScheduledExecutor();

    @Override
    public void afterPropertiesSet() throws Exception {
        // 当前Bean的声明周期回调时初始化定时任务
        SINGLE_POOL.scheduleAtFixedRate(() -> {

            // 1、查询全量数据
            // 2、在写锁中,写缓存
            writeAll(allAgency);

        }), 0, 100, TimeUnit.SECONDS);
    }
}

    



项目上的使用方式如下,写锁与查询数据并写入缓存对应, 读锁与读取缓存数据对应【读与读可并行】。

@Slf4j
@Repository
public class MdmAgencyCache implements InitializingBean, CommandLineRunner {

    /**
     * 缓存 {@code CONF_DELY_SHIPPING_CONDITION}数据
     */
    private static final Set<RemoteAgencyDTO> CACHE = new ConcurrentHashSet<>(50);

    private final MdmFeignService mdmFeignService;

    public MdmAgencyCache(MdmFeignService mdmFeignService) {
        this.mdmFeignService = mdmFeignService;
    }

    @Override
    public void run(String... args) {
        final List<RemoteAgencyDTO> allAgency = mdmFeignService.getAllAgency();
        log.info("mdmFeignService RemoteAgencyDTO list= {}", JSON.toJSONString(allAgency));
        if (CollectionUtil.isEmpty(allAgency)) {
            log.error("获取全量 *** 信息错误");
            throw new RuntimeException("获取全量 *** 信息错误");
        }

        writeAll(allAgency);
    }

    /**
     * 读写锁
     */
    private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();
    private final Lock readLock = READ_WRITE_LOCK.readLock();
    private final Lock writeLock = READ_WRITE_LOCK.writeLock();

    @Override
    public void afterPropertiesSet() throws Exception {

    }

    /**
     * 写入操作
     * @param value 单条数据
     */
    public void writeAll(List<RemoteAgencyDTO> value) {
        writeLock.lock();
        try {
            CACHE.addAll(value);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * 办事处编码获取办事处名称
     *
     * @param agencyCode ***编码
     * @return ***名称
     */
    public String getAgencyName(String agencyCode) {
        readLock.lock();
        try {
            for (RemoteAgencyDTO dto : CACHE) {
                if (dto.getCode().equals(agencyCode)) {
                    return dto.getName();
                }
            }
            return null;
        } catch (Exception e) {
            return null;
        } finally {
            readLock.unlock();
        }
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值