一、基于版本号实现乐观锁
基于版本号实现乐观锁是比较常见的一种实现方式。原理是在数据表中增加一个版本号字段,每次更新数据的时候,将版本号加1,并且在更新语句中带上版本号的判断条件。
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `version` int(11) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; public int updateUser(User user) { String sql = "update user set name = ?, version = ? where id = ? and version = ?"; return jdbcTemplate.update(sql, user.getName(), user.getVersion() + 1, user.getId(), user.getVersion()); }
上述代码中,updateUser方法首先会根据传入的User对象生成update SQL语句,并带上当前版本号+1作为更新后的版本号,以及当前记录的ID和版本号作为更新条件。在更新数据的同时,还要判断更新前后的版本号是否一致,以保证数据的一致性。
二、基于CAS实现乐观锁
基于CAS(Compare And Swap)实现乐观锁是一种更为底层的实现方式。基于CAS的实现方式通常不需要数据库支持。CAS本质上是一种原子操作,它可以保证在多线程环境下变量的原子性。由于CAS不需要加锁,因此性能比较高。
CAS的基本原理是:先读取变量的值,同时保存一个副本,之后用新值与原值比较,如果相等,则将变量的值更新为新值,否则不做操作。在Java中,CAS操作由java.util.concurrent.atomic包下的一系列类提供,例如AtomicInteger、AtomicLong等。
public void updateUser(User user) { AtomicReferenceuserReference = new AtomicReference (user); User newUser = new User(); newUser.setId(user.getId()); newUser.setName(user.getName()); while (!userReference.compareAndSet(user, newUser)) { user = userReference.get(); newUser = new User(); newUser.setId(user.getId()); newUser.setName(user.getName()); } }
上述代码中,我们通过AtomicReference类型的对象来引用需要更新的User对象,如果当前值与期望值相同,则用新值替换掉旧的值,否则一直循环直到更新成功。
三、基于Redis实现乐观锁
Redis是一个内存数据库,使用Redis也可以实现乐观锁。Redis提供了set命令支持,用来设置一个key对应的value。
在使用Redis实现乐观锁的时候,我们需要将version存放在Redis中。具体实现方式是:在更新数据之前,先从Redis中取出version,如果与当前记录中的version一样,则将version加1,同时更新数据。否则,说明当前数据已经被其他线程更新过,我们需要重试或者给出相应的错误提示。
public void updateUser(String redisKey, User user) { Jedis jedis = null; try { jedis = jedisPool.getResource(); String versionKey = redisKey + "_version"; String version = jedis.get(versionKey); if (version == null || Integer.parseInt(version) == user.getVersion()) { jedis.set(versionKey, String.valueOf(user.getVersion() + 1)); jedis.set(redisKey, JSON.toJSONString(user)); } else { throw new OptimisticLockException(); } } finally { if (jedis != null) { jedis.close(); } } }
上述代码中,我们通过Jedis类型的对象操作Redis。首先从Redis中取出version,然后判断当前版本号是否与Redis中的版本号相同,如果相同,则对version进行加1操作,同时更新数据;否则抛出自定义的OptimisticLockException异常。