注解封装一个公平锁

需求

  • 项目里有个场景,读的并发很大,但仅仅涉及到很少的写操作。那么此时采用读写锁就是个不错的选择,但是默认的读写锁是非公平锁,如果读并发很大,写请求到达时可能抢不到锁,所以此时需要使用公平读写锁,为了提升复用性,采取注解的形式进行封装。
  • 如果一个class的object需要这个能力,加入注解@ReadWriteResource
  • 具体的函数,如果加@ReadOnlyOperator注解,那么表明这是一个读任务,可以并发执行;
  • 如果加@WriteOperator,那么表明这是一个写任务,和读任务、写任务互斥执行;
    {% note info no-icon %}
  • 需要注意的是,这里需要控制锁粒度为对象级别的锁。
    {% endnote %}
// 伪代码如下
@ReadWriteResource
class ManagedResource {

    @ReadOnlyOperator
    String getResource(int key);

    @ReadOnlyOperator
    int getName(int key);

    @WriteOperator
    void updateResource(int key, String resource);
}

具体实现

  • 采用注解+动态代理的方式来实现,思路就是根据@ReadWriteResource判断该对象是否需要动态代理,然后拦截对象中的方法,在方法执行前后加锁释放。

    1. 首先创建三个注解
      {% tabs 注解封装公平读写锁 %}
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface ReadWriteResource {
    }
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ReadOnlyOperator {
    }
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface WriteOperator {
    }
    

    {% endtabs %}
    2. 然后编写一个代理工厂类,用于创建代理对象,首先判断对象的Class是否有@ReadWriteResource注解,以此判断是否要为该对象创建代理对象

    public class ReadWriteResourceProxyFactory<T> {
    
        public T createProxy(T target) {
            Class<?> clazz = target.getClass();
            // 判断该类是否有ReadWriteResource注解
            ReadWriteResource annotation = clazz.getAnnotation(ReadWriteResource.class);
            // 如果有注解,则创建代理对象
            if (annotation != null) {
                // 这里并不需要将target对象传入,因为拦截器拦截的是目标对象的方法,而不是目标对象本身
                ReadWriteResourceInterceptor interceptor = new ReadWriteResourceInterceptor();
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(clazz);
                enhancer.setCallback(interceptor);
                return (T) enhancer.create();
            }
            // 如果没有注解,直接返回原对象
            return target;
        }
    }
    
    1. 构造函数先为对象初始化读写锁,拦截逻辑中判断方法上是否存在@ReadOnlyOperatorWriteOperator注解,来确定是读操作还是写操作,然后加上对应的锁。
    public class ReadWriteResourceInterceptor implements MethodInterceptor {
        private final Lock readLock;
        private final Lock writeLock;
    
        public ReadWriteResourceInterceptor() {
            ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
            this.readLock = readWriteLock.readLock();
            this.writeLock = readWriteLock.writeLock();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            ReadOnlyOperator readOnlyOperatorAnnotation = method.getAnnotation(ReadOnlyOperator.class);
            WriteOperator writeOperatorAnnotation = method.getAnnotation(WriteOperator.class);
    
            if (readOnlyOperatorAnnotation != null) {
                // 如果包含ReadOnlyOperator注解,加读锁
                readLock.lock();
                try {
                    // 执行原方法
                    return methodProxy.invokeSuper(o, objects);
                } finally {
                    // 释放读锁
                    readLock.unlock();
                }
            } else if (writeOperatorAnnotation != null) {
                // 如果包含WriteOperator注解,加写锁
                writeLock.lock();
                try {
                    // 执行原方法
                    return methodProxy.invokeSuper(o, objects);
                } finally {
                    // 释放写锁
                    writeLock.unlock();
                }
            } else {
                // 没加注解的普通方法,返回直接调用原方法
                return methodProxy.invokeSuper(o, objects);
            }
        }
    }
    
    1. 基本使用,伪代码如下,测试的时候可以在日志中打印出读写锁地址
    ReadWriteResourceProxyFactory<ManagedResource> proxyFactory = new ReadWriteResourceProxyFactory<>();
    ManagedResource proxy = proxyFactory.createProxy(new ManagedResource());
    proxy.getName();
    proxy.updateResource();
    
    • 写完了打包上传至maven私服,以后就是封装自己的工具类了

AOP实现

  • 之前其实采用的是注解+AOP来封装的,然后写着写着就发现了点问题,这里也来和大家分享一下吧。
  • 首先注解方面没有变化,不过我没用到@ReadWriteResource注解,因为找切点的时候可以直接根据方法上是否有@ReadOnlyOperator@WriteOperator来判断。
  • AOP的逻辑如下
// 明天更
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值