注解封装公平读写锁
需求
- 项目里有个场景,读的并发很大,但仅仅涉及到很少的写操作。那么此时采用读写锁就是个不错的选择,但是默认的读写锁是非公平锁,如果读并发很大,写请求到达时可能抢不到锁,所以此时需要使用公平读写锁,为了提升复用性,采取注解的形式进行封装。
- 如果一个class的object需要这个能力,加入注解
@ReadWriteResource
- 具体的函数,如果加
@ReadOnlyOperator
注解,那么表明这是一个读任务,可以并发执行; - 如果加
@WriteOperator
,那么表明这是一个写任务,和读任务、写任务互斥执行;
1 | // 伪代码如下 |
具体实现
-
采用注解+动态代理的方式来实现,思路就是根据
@ReadWriteResource
判断该对象是否需要动态代理,然后拦截对象中的方法,在方法执行前后加锁释放。- 首先创建三个注解
1
2
3
4
public ReadWriteResource {
}1
2
3
4
public ReadOnlyOperator {
}1
2
3
4
public WriteOperator {
}- 然后编写一个代理工厂类,用于创建代理对象,首先判断对象的Class是否有
@ReadWriteResource
注解,以此判断是否要为该对象创建代理对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public 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
注解,来确定是读操作还是写操作,然后加上对应的锁。
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
41public 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();
}
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
2
3
4ReadWriteResourceProxyFactory<ManagedResource> proxyFactory = new ReadWriteResourceProxyFactory<>();
ManagedResource proxy = proxyFactory.createProxy(new ManagedResource());
proxy.getName();
proxy.updateResource();- 写完了打包上传至maven私服,以后就是封装自己的工具类了
AOP实现
- 之前其实采用的是注解+AOP来封装的,然后写着写着就发现了点问题,这里也来和大家分享一下吧。
- 首先注解方面没有变化,不过我没用到
@ReadWriteResource
注解,因为找切点的时候可以直接根据方法上是否有@ReadOnlyOperator
和@WriteOperator
来判断。 - AOP的逻辑如下
1 | // 明天更 |
评论