并发-LinkedBlockingQueue
1、背景:
通过中间件获取分布式唯一id,同事做了这么一个优化,每次获取10个,存在一个队列里,每次只取一个,用完了再获取10个。
2、问题代码:
@Component
public class IdUtil {
private static final Logger log = LoggerFactory.getLogger(IdUtil.class);
@Value("${distributed.step:10}")
private Integer distributedStep;
KrillIDGeneratorFactory krillIDGeneratorFactory = SpringContextHolder.getBean(KrillIDGeneratorFactory.class);
KrillIDGenerator krillIDGenerator = krillIDGeneratorFactory.createDBIDGenerator("trade-order-profit-id");
private static Queue<Long> queue = new LinkedBlockingQueue<>();
public List<Long> getIds() {
try {
return krillIDGenerator.getNextIds(distributedStep);
} catch (KrillServiceException e) {
log.error("批量获取分布式ids失败", e);
throw new BizException(BizErrorCodeEnum.SYSTEM_ERROR);
}
}
public Long getNext() {
if (queue.isEmpty()) {
List<Long> ids = getIds();
log.info("ids:{}",ids);
queue.addAll(ids);
}
Long pollId = queue.poll();
log.info("分配的分布式id为:{}", pollId);
return pollId;
}
}
问题就是:判空的时候可能还不为空,但是poll的时候queue空了。【超卖问题】
3、解决方案
此时,粗暴的处理方式就是:
直接在方法上加synchronized,方法的锁,锁的是对象本身,由于是单例的,所以锁这个没问题。
【tips】获取唯一id,就算这个bug没解决,使用方也有必要对取的值判空,防止业务流程受影响。
4、写demo测试下:
/**
* @author liuxuewei
*/
public class Test22222 {
private static Queue<Long> queue = new LinkedBlockingQueue<>();
// private static List<Long> queue = new ArrayList<>();
public static void main(String[] args) {
for (int j = 0; j < 100; j++) {
int finalJ = j;
new Thread(()->{
getNext(finalJ);
}).start();
}
}
public static List<Long> getIds(int j) {
try {
//从中间件获取唯一id的伪代码
List<Long> a = new ArrayList();
for (int i = 0; i < 10; i++) {
a.add((long)10*j+i);
}
return a;
} catch (Exception e) {
System.out.println("失败");
}
return null;
}
public synchronized static Long getNext(int j) {
if (queue.isEmpty()) {
List<Long> ids = getIds(j);
// System.out.println("新增ids:"+ids);
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
queue.addAll(ids);
System.out.println("当前"+queue);
}
try { //休息下,更好的暴露问题
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Long pollId = queue.poll();
System.out.println("分配"+pollId);
return pollId;
}
}
没加锁
加了锁
5、LinkedBlockingQueue是线程安全的吗?
可以看到,没加锁,也没有出现并发修改异常这个报错。【如果是arraylist的话就直接报错了】,所以我们认为LinkedBlockingQueue的addAll是线程安全的。
看这段解释:
意思就是说,虽然是线程安全的,但是多个addAll一起并发的话,可能会见缝插针,每组的顺序是可以保证的,但是每组的数据不一定挨在一起。
评论区