实体
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* @Description: incr
* @Date: 2023-05-06
* @Version: V1.0
*/
@Data
@TableName("t_incr")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="incr对象", description="incr")
public class Incr implements Serializable {
private static final long serialVersionUID = 1L;
/**key,唯一*/
@TableId(type = IdType.ASSIGN_ID)
private String keyInfo;
/**增长到多少*/
private Integer valueInfo;
}
服务
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 获取自增数据
*/
@Service
@Slf4j
public class IncrementManage extends ServiceImpl<IncrementMapper, Incr> {
@Autowired
private IncrementMapper incrementMapper;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 获取自增数据
*
* @param key
* @return
*/
public String getNext(String key) {
try {
// 尝试获取是否存在
Incr incr = incrementMapper.selectById(key);
if (incr == null) {
incr = new Incr();
incr.setKeyInfo(key);
incr.setValueInfo(1);
incrementMapper.insert(incr);
} else {
incr = getIncr(key);
}
return String.format("%04d", incr.getValueInfo());
} catch (DuplicateKeyException e) {
// 出现key重复问题
Incr incr = getIncr(key);
return String.format("%04d", incr.getValueInfo());
} catch (Exception e) {
throw new RuntimeException("获取自增的数据失败,key:" + key, e);
}
}
/**
* 获取自增数据,并补全位数
*
* @param key
* @return
*/
public String getNextCompletion(String key, int num) {
// 获取自增的数据
String nextValue = getNext(key);
if (nextValue == null) {
return null;
}
StringBuilder incr = new StringBuilder(nextValue);
for (int i = incr.length(); i < num; i++) {
incr.insert(0, "0");
}
return incr.toString();
}
/**
* 以db锁获取自增
*
* @param key
* @return
*/
private Incr getIncr(String key) {
Incr incr = transactionTemplate.execute(status -> {
Incr rs = incrementMapper.selectForUpdate(key);
rs.setValueInfo(rs.getValueInfo() + 1);
incrementMapper.updateById(rs);
return rs;
});
return incr;
}
}
sql
SELECT * FROM t_incr WHERE key_info = #{key} FOR UPDATE
集成测试
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Slf4j
@ActiveProfiles("test")
@Import(IncrementManage.class)
public class IncrementManageIntegrationTest {
@Autowired
private IncrementManage incrementManage;
@Autowired
private IncrementMapper incrementMapper;
@Test
public void getNextTest() {
String key = "testKey";
String value = incrementManage.getNext(key);
Assertions.assertNotNull(value);
}
@Test
public void getNextCompletionTest() {
String key = "testKey";
int num = 6;
String value = incrementManage.getNextCompletion(key, num);
Assertions.assertNotNull(value);
Assertions.assertEquals(num, value.length());
}
@Test
public void concurrentGetNextTest() throws InterruptedException {
Set<String> sets = new HashSet<>();
ExecutorService executorService = Executors.newFixedThreadPool(10); // Use a thread pool with 10 threads
String key = "testKey";
// Submit 100 tasks to the executor
IntStream.range(0, 100).forEach(i -> executorService.submit(() -> {
String value = incrementManage.getNext(key);
Assertions.assertNotNull(value);
sets.add(value);
}));
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES); // Wait for all tasks to complete
System.out.println(sets.toString());
System.out.println(sets.size()+"");
Assertions.assertTrue(sets.size()==100);
}
@Test
public void concurrentGetNextCompletionTest() throws InterruptedException {
Set<String> sets = new HashSet<>();
ExecutorService executorService = Executors.newFixedThreadPool(10); // Use a thread pool with 10 threads
String key = "testKey";
int num = 6;
// Submit 100 tasks to the executor
IntStream.range(0, 100).forEach(i -> executorService.submit(() -> {
String value = incrementManage.getNextCompletion(key, num);
Assertions.assertNotNull(value);
Assertions.assertEquals(num, value.length());
sets.add(value);
}));
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES); // Wait for all tasks to complete
System.out.println(sets.toString());
System.out.println(sets.size()+"");
Assertions.assertTrue(sets.size()==100);
}
}
通过数据库的db锁来得到自增的数据
以下代码会产生死锁,最终导致并发结果不正确
原因: 1、在一个事务中,select会产生一个s锁(读锁),当获取了读锁,则第二个事物要想获取x锁,则必须等待读锁释放,但是可以获取读锁。
2、在一个事务中,获取了写锁,则其他事物既不能获取读锁,也不能获取写锁
比如
时间1:事物1:获取s锁,事物2:获取s锁
时间2:事物1:尝试获取写锁,但是事物2存s锁,等待事物1释放s锁, 事物2:尝试获取写锁,但是事物1有读锁,等待事物1释放读锁,最终导致了相互之间的死锁
解决方法如上:将读提取出来,不在事物中,或者第一步就获取写锁(for update)
package com.cngzh.traceability.biz.incr.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cngzh.traceability.biz.incr.entity.Incr;
import com.cngzh.traceability.biz.incr.mapper.IncrementMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 获取自增数据
*/
@Service
@Slf4j
public class IncrementManage extends ServiceImpl<IncrementMapper, Incr> {
@Autowired
private IncrementMapper incrementMapper;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 获取自增数据
*
* @param key
* @return
*/
public String getNext(String key) {
String rs = transactionTemplate.execute(status -> {
try {
// 尝试获取是否存在
Incr incr = incrementMapper.selectById(key);
if (incr == null) {
incr = new Incr();
incr.setKeyInfo(key);
incr.setValueInfo(1);
incrementMapper.insert(incr);
} else {
incr = getIncr(key);
}
return String.format("%04d", incr.getValueInfo());
} catch (DuplicateKeyException e) {
// 出现key重复问题
Incr incr = getIncr(key);
return String.format("%04d", incr.getValueInfo());
} catch (Exception e) {
throw new RuntimeException("获取自增的数据失败,key:" + key, e);
}
});
return rs;
}
/**
* 获取自增数据,并补全位数
*
* @param key
* @return
*/
public String getNextCompletion(String key, int num) {
// 获取自增的数据
String nextValue = getNext(key);
if (nextValue == null) {
return null;
}
StringBuilder incr = new StringBuilder(nextValue);
for (int i = incr.length(); i < num; i++) {
incr.insert(0, "0");
}
return incr.toString();
}
/**
* 以db锁获取自增
*
* @param key
* @return
*/
private Incr getIncr(String key) {
Incr rs = incrementMapper.selectForUpdate(key);
rs.setValueInfo(rs.getValueInfo() + 1);
incrementMapper.updateById(rs);
return rs;
// return incr;
}
}