需求
- 项目里有个场景,读的并发很大,但仅仅涉及到很少的写操作。那么此时采用读写锁就是个不错的选择,但是默认的读写锁是非公平锁,如果读并发很大,写请求到达时可能抢不到锁,所以此时需要使用公平读写锁,为了提升复用性,采取注解的形式进行封装。
- 如果一个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
判断该对象是否需要动态代理,然后拦截对象中的方法,在方法执行前后加锁释放。- 首先创建三个注解
{% 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; } }
- 构造函数先为对象初始化读写锁,拦截逻辑中判断方法上是否存在
@ReadOnlyOperator
或WriteOperator
注解,来确定是读操作还是写操作,然后加上对应的锁。
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); } } }
- 基本使用,伪代码如下,测试的时候可以在日志中打印出读写锁地址
ReadWriteResourceProxyFactory<ManagedResource> proxyFactory = new ReadWriteResourceProxyFactory<>(); ManagedResource proxy = proxyFactory.createProxy(new ManagedResource()); proxy.getName(); proxy.updateResource();
- 写完了打包上传至maven私服,以后就是封装自己的工具类了
- 首先创建三个注解
AOP实现
- 之前其实采用的是注解+AOP来封装的,然后写着写着就发现了点问题,这里也来和大家分享一下吧。
- 首先注解方面没有变化,不过我没用到
@ReadWriteResource
注解,因为找切点的时候可以直接根据方法上是否有@ReadOnlyOperator
和@WriteOperator
来判断。 - AOP的逻辑如下
// 明天更