需求

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

    @ReadOnlyOperator
    String getResource(int key);

    @ReadOnlyOperator
    int getName(int key);

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

具体实现

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

    1. 首先创建三个注解
      1
      2
      3
      4
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      public @interface ReadWriteResource {
      }
      1
      2
      3
      4
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface ReadOnlyOperator {
      }
      1
      2
      3
      4
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface WriteOperator {
      }
    2. 然后编写一个代理工厂类,用于创建代理对象,首先判断对象的Class是否有@ReadWriteResource注解,以此判断是否要为该对象创建代理对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      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;
      }
      }
    3. 构造函数先为对象初始化读写锁,拦截逻辑中判断方法上是否存在@ReadOnlyOperatorWriteOperator注解,来确定是读操作还是写操作,然后加上对应的锁。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      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);
      }
      }
      }
    4. 基本使用,伪代码如下,测试的时候可以在日志中打印出读写锁地址
      1
      2
      3
      4
      ReadWriteResourceProxyFactory<ManagedResource> proxyFactory = new ReadWriteResourceProxyFactory<>();
      ManagedResource proxy = proxyFactory.createProxy(new ManagedResource());
      proxy.getName();
      proxy.updateResource();
    • 写完了打包上传至maven私服,以后就是封装自己的工具类了

AOP实现

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