侧边栏壁纸
博主头像
这就是之谦博主等级

我们的征途是星辰大海

  • 累计撰写 182 篇文章
  • 累计创建 3 个标签
  • 累计收到 16 条评论
标签搜索

目 录CONTENT

文章目录

并发-LinkedBlockingQueue

这就是之谦
2023-04-07 / 0 评论 / 0 点赞 / 718 阅读 / 664 字
温馨提示:
本文最后更新于 2023-04-07,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

并发-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空了。【超卖问题】

image-20230407103436614

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;
    }
}

没加锁

image-20230407104041432

加了锁

image-20230407104014702

5、LinkedBlockingQueue是线程安全的吗?

可以看到,没加锁,也没有出现并发修改异常这个报错。【如果是arraylist的话就直接报错了】,所以我们认为LinkedBlockingQueue的addAll是线程安全的。

看这段解释:

image-20230407104202265

意思就是说,虽然是线程安全的,但是多个addAll一起并发的话,可能会见缝插针,每组的顺序是可以保证的,但是每组的数据不一定挨在一起。

0

评论区