Spring 加锁的方法
一、使用 synchronized 关键字
这是最简单的加锁方式,适用于单实例环境。它可以作用于方法或代码块,确保同一时刻只有一个线程可以执行被锁定的代码。
public class MyService {
public synchronized void method1() {
// 临界区代码
}
}
二、使用 ReentrantLock
ReentrantLock 提供了比 synchronized 更多的控制,例如公平锁、可中断的锁等待等。
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private final ReentrantLock lock = new ReentrantLock();
public void method1() {
// 上锁
lock.lock();
try {
// 临界区代码
} finally {
// 解锁
lock.unlock();
}
}
}
三、 使用自定义注解和AOP实现
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {
// 一般可以设置一个默认的时间结点
// default
}
// AOP 切面
@Aspect
@Component
public class LockingAspect {
private final ReentrantLock lock = new ReentrantLock();
// 横切点,我i喜欢这样写
@Pointcut("@annotation(com.smart.web.aop.annotation.DockLogger)")
public void doJoinPointCut() {
}
@Around("@doJoinPointCut()")
public Object lockMethod(ProceedingJoinPoint pjp) throws Throwable {
lock.lock();
try {
return pjp.proceed();
} finally {
lock.unlock();
}
}
}
四、分布式锁
在集群环境中,需要使用分布式锁,如基于 Redis 的 RedLock 或使用 ZooKeeper 的 Curator Lock。
// 基于 Redis 的分布式锁示例
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void method1() {
String lockKey = "my-lock";
boolean locked = false;
try {
locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
if (locked) {
// 临界区代码
} else {
// 锁已被其他节点获取
}
} finally {
if (locked) {
redisTemplate.delete(lockKey);
}
}
}
🔺注意:前两种方法会与事务发生异常
事务和锁发生的异常
这两种锁在在和@Transational同一个方法一起使用的情况下会出现,锁已经去除,但是事务还没提交的情况,造成脏读和数据不一致性等情况.
由于事务的提交在方法运行结束之后,并且事务真正启动在执行到它们之后的第一个操作 InnoDB 表的语句的时候。所以会出现这种情况:
(图片是从网上copy的,不想画了,反正说的都一样)
原因:
spring@Transactional注解使用的是AOP来实现,也就是说被@Transactional注解的方法的事务是由spring生成的一个代理类来处理的,当一个线程执行完该方法并释放锁后,代理类还没有提交事务前,别的线程是有机会进入到该方法中的,这样一来,就有几率访问到过期的数据,从而导致并发问题。
解决方法一:可以把@Transational和业务逻辑的代码单独提到一个service里。
public synchronized boolean methodName(String id) {
return this.update(id);
}
@Transactional
public void update(String id) {
/*
业务代码
*/
}
解决方法二:利用AOP,
懒得写了,和方法三差不多,也可以说就是直接用方法三