乐观锁实操
继上篇关于使用@PostConstruct注解提到的乐观锁,讲讲乐观锁的使用方法。
在实操前讲讲什么叫做乐观锁、悲观锁。
悲观锁:从Java多线程角度,存在着“可见性、原子性、有序性”三个问题,悲观锁就是假设在实际情况中存在着多线程对同一共享的竞争,所以在操作前先占有共享资源(悲观态度)。因此,悲观锁是阻塞,独占的,存在着频繁的线程上下文切换,对资源消耗较大。synchronized就是悲观锁的一种实现。
乐观锁:如名一样,每次操作都认为不会发生冲突,尝试执行,并检测结果是否正确。如果正确则执行成功,否则说明发生了冲突,回退再重新尝试。乐观锁的过程可以分为两步:冲突检测 和 数据更新。在Java多线程中乐观锁一个常见实现即:CAS操作。
CAS(Compare-And-Swap,比较和替换)。其具有三个操作数:内存地址V,旧的预期值A,新的预期值B。当V中的值和A相同时,则用新值B替换V中的值,否则不执行更新。(PS:上述的操作是原子性的,因为过程是:要么执行更新,要么不更新)
CAS存在的问题:
1.ABA问题值,内存地址V的值是A,线程one从V中取出了A,此时线程two也从V中取出了A,同时将A修改为B,但是又因为一些原因修改为A。而此时线程one仍看到V中的值为A,认为没有发生变化,此为ABA问题。解决ABA问题一种方式是通过版本号(version)。每次执行数据修改时,都需要带上版本号,如:1A,2B,3A。通过比较版本号可知是否有发生过操作,也就解决了ABA问题。
2.未知的等待时长,因为CAS采取失败重试的策略,所以不确定会发生多少次循环重试。如果在竞争激烈的环境下,其重试次数可能大幅增加。此时效率也就降低了。
了解了什么是乐观锁、悲观锁及CAS后,下面写上本人工作中乐观锁的简单运用:
背景:项目用的分布式系统,在做一个定时扫描数据并将数据分发到另一个项目相关需求。
1.建表:
DROP TABLE IF EXISTS "public"."t_log";
CREATE TABLE "public"."t_log" (
"version" int4 NOT NULL DEFAULT 0,
"update_time" timestamp(6),
"code" varchar(20) COLLATE "pg_catalog"."default" NOT NULL
);
COMMENT ON COLUMN "public"."t_log"."version" IS '乐观锁版本';
COMMENT ON COLUMN "public"."t_log"."update_time" IS '更新时间';
COMMENT ON COLUMN "public"."t_log"."code" IS '乐观锁事件(可扩展)';
ALTER TABLE "public"."t_log" ADD CONSTRAINT "t_log_pk" PRIMARY KEY ("code");
2.mapper:
//获取最新版本
@Select("SELECT aversion FROM t_log WHERE code = 'xxx'")
Integer getVersion();
//更新版本1成功,0失败
@Update("UPDATE t_log SET version = version + 1 , update_time = now() WHERE code = 'xxx' and version = #{version}")
Integer updateVersion(Integer version);
3.代码片段:
@Component
public class Task {
@Autowired
private XxxMapper xxxMapper;
@Autowired
private RecordDistributeMapper recordDistributeMapper;
@Autowired
private OrderMapperorderMapper;
/** 乐观锁版本号 */
private static Integer version;
@PostConstruct
public void initVersion() {
version = xxxMapper.getVersion();
}
@Scheduled(cron = "0 0 * * * ?")
@Transactional( rollbackFor = Exception.class)
public void reSendHetRecord() {
// 更新乐观锁成功,方可执行任务,然后存储最新版本号 1成功,0失败
Integer lineNumber = xxxMapper.updateVersion(hetRecordVersion);
if (lineNumber == 1){
LOG.info("执行record");
List<Xxx> records = recordDistributeMapper.getRecords();
if (!records.isEmpty()){
for (Record record : records) {
try {
Order order = orderMapper.getOrder(record.getOrderNo());
if (Cons.XXX.equals(order.getOrderCode().getValue())){
distributeRecord(external.getXxxUrl(),record);
}
} catch (Exception e) {
LOG.error("error 重发分发处理记录信息失败:" + record+"——————message:"+e.getMessage());
}
}
}
version += 1;
}else {
LOG.info("未执行record");
}
}
}
这是项目中运用到的一点乐观锁,希望对您理解乐观锁有帮助。
悲观锁以后项目遇到实操再做更新。
如有错误,敬请斧正,以防误导他人。