美文网首页
Spring与Java中的锁

Spring与Java中的锁

作者: 柚子过来 | 来源:发表于2018-06-07 15:07 被阅读0次

两种情况分析:1、@Transactional+Synchronized。2、AOP+锁。扩展:分布式锁
point:文章中的内容本人并没有在实际中用过也没有测试过,只是想到了这个问题所以按自己目前的理解做一个记录,正确性待验证。

@Transactional+Synchronized

在使用Spring的@Transactional处理事务时虽然有事务隔离机制,但是在SERIALIZABLE以下的隔离级别并不能解决并发情况下的幻读、脏读问题。看下面的一个例子:

@Transactional
public void update() {
    boolean index = select(....);
    if(index) {
        update( ...index = false...,);
        doSomthing()      
    }
}

@Transactional默认的隔离级别是可重复读,按道理说是没有脏读、不可重复读等问题的,但是细想一下,假如以上代码是为了保证doSomthing()只被执行一次。如果两个线程对应的事务A和事务B都进行了select操作(因为该隔离级别下读不阻塞),返回都为true。然后事务A进行了update并执行了doSomthing()。当A执行完updat,B又会接着往下执行,会再执行一次doSomthing()。

所以就需要业务层来实现严格的同步,Java中锁实现有Synchronized和ReentrantLock两种,代码可能是这个样子的:

private ReentrantLock lock = new ReentrantLock();
@Transactional
public void update() {
    lock.lock();
    try {
        boolean index = select(....);
        if(index) {
            update( ...index = false...,);
            doSomthing()
        }
    }finally {
        lock.unlock();
    }
}
------------------------
@Transactional
public synchronized void update() {
    boolean index = select(....);
    if(index) {
        update( ...index = false...,);
        doSomthing()
    }
}

不管是Synchronized还是ReentrantLock,其实这样写都还是不能保证同步,因为Spring的@Transactional是AOP实现的,它是在函数开始前开启事务,在函数执行完毕后提交事务。所以上面代码中锁的释放是在事务提交之前,所以还是会有前面提到的问题。

AOP+锁

上面提到的锁无效是因为锁的范围是在AOP事务范围之内,那针对这个的解决方案可以使用AOP+锁实现,我们可以自定义一个锁注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLock {
}

然后定义一个AOP切面:

@Component
@Aspect
public class LockAspect implements Ordered {

private static ReentrantLock lock = new ReentrantLock();
@Pointcut("@annotation(MyLock)")
public void dolock(){}

@Around("dolock()")
public void testLock(ProceedingJoinPoint joinPoint) {
    lock.lock();
    try {
        joinPoint.proceed();
    } catch (Throwable throwable) {
        ...
    }finally {
        lock.unlock();
    }
}
public int getOrder() {
    return Ordered.LOWEST_PRECEDENCE;          //小的先执行
 }
}

然后我们就可以这样实现锁同步:

@MyLock
@Transactional
public synchronized void update() {
    boolean index = select(....);
    if(index) {
        update( ...index = false...,);
        doSomthing()
    }
}
分布式锁

上面的锁实现在分布式环境下都是无效的,因为分布式下lock不是一个对象。我了解的解决方案有下面几个:

1、自己通过数据库实现锁
既然reentranLock和Synchronized不能使用的原因是锁对象不是同一个,那是不是可以通过数据库来实现同一个锁对象?在数据库中定义一个锁表,插入一个状态记录(比如0/1、true/false),当线程读取数据库记录是0的时候就视为获取锁,并将状态修改为1。当业务操作执行完要释放锁的时候就将状态改回0。或者直接利用数据库的唯一性约束,设置唯一主键,每次获取锁的操作都是一个insert操作,因为唯一性约束所以只会有一个线程insert成功,即获得锁,释放锁时delete该条记录就行。

this.SQL_TRY_ACQUIRE_LOCK = "update " + props.getTableName() + " set  locked = true ,token = ? ,start_time = ? ,expire_time = ? ,end_time = null 
                             where id = ? and (locked = false or expire_time < ?) ";
this.SQL_SELECT_LOCK = "select  id ,name ,locked ,token ,start_time ,end_time ,expire_time ,attributes from " + props.getTableName() + " 
                             where id = ? ";

public DLock acquireLock(DLockType type, String lockToken, long currTime, long expireTime) {
    this.template.update(this.SQL_TRY_ACQUIRE_LOCK, new Object[]{lockToken, new Timestamp(currTime), new Timestamp(expireTime), type.getId(), new Timestamp(currTime)});
    List<DLock> locks = this.template.query(this.SQL_SELECT_LOCK, new Object[]{type.getId()}, this.rowMapper);
    if (locks != null && !locks.isEmpty()) {
        DLock lock = (DLock)locks.get(0);
        if (lockToken.equals(lock.getLockToken())) {
            return lock;
        } else {
            long sysCurrTime = System.currentTimeMillis();
            if (!lock.isLocked() || lock.getExpireTime() != null && lock.getExpireTime() >= currTime) {
                return null;
            } else {
                logger.debug("LockExpireTime={}, CurrTime={}, SysCurrTime={}", new Object[]{lock.getExpireTime(), currTime, sysCurrTime});
                return lock;
            }
        }
    } else {
        return null;
    }
}

以上代码先update,如果锁空闲就可以update成功,并且update的时候数据库锁会保证其他线程不能操作该条数据。

2、利用缓存如redis实现

3、利用zookeeper实现

相关文章

网友评论

      本文标题:Spring与Java中的锁

      本文链接:https://www.haomeiwen.com/subject/veywsftx.html