记一次无锁设计

156 阅读2分钟

一.背景

多线程写入到一个集合中,当集合大于N时,flush当前集合,并且用一个新的集合代替写入,不能全局加锁

二.实现

1. 如果全局加锁,那么可以先在写入前判断集合大小,然后再插入,但是不加锁,先判断集合大小,再插入可能会导致多个线程同时拿到集合大小满足条件的值,插入后超过集合大小,比如
    List list;
    void put(int value){
1.        if(list.size<10){
2.            list.put(valus);
        }
    }

3个线程走到1处,size都为9,然后再都执行2,size会超过10

2.如果预先判断坑位,有坑位再写入,没有就flush
    AtomicInteger preWrite;
    void put(int value){
        int count=preWrite.incrementAndGet();
1.        if(count>10){
            synchronized{
                flush(list);
                list=new ArrayList();
                preWrite.set(0);
                }
2.            preWrite.incrementAndGet();
            }
            list.write(value);
        }
    }

代码2处的原因是经过if之后,preWrite的预占位被清掉了进行补偿
可能出现的问题:

  1. 线程A count=11
  2. 线程B count=5 然后让出cpu停留在代码1处
  3. 这时候A进行flush,list的大小就只有9
  4. 下一个list可能就有11这么大
3.加入写完判断
    AtomicInteger preWrite;
    AtomicInteger afterWrite;
    void put(int value){
1.        int count=preWrite.incrementAndGet();
        if(count>10){
            synchronized{
                while(afterWrite.get()!=10)
                 Thread.sleep(0);
                flush(list);
                list=new ArrayList();
2.              preWrite.set(0);
                afterWrite.set(0);
                }
            preWrite.incrementAndGet();
            }
            list.write(value);
            afterWrite..incrementAndGet();
        }
    }
  1. 假设线程A运行到代码2处
  2. 线程B运行到代码1处然后执行完put
  3. 这时候线程A继续执行,afterWrite会被置为0
  4. 这样导致的问题是after少了一次,while会被一直卡住
4.调整顺序
    AtomicInteger preWrite;
    AtomicInteger afterWrite;
    void put(int value){
1.        int count=preWrite.incrementAndGet();
        if(count>10){
            synchronized{
                while(afterWrite.get()!=10)
                 Thread.sleep(0);
                flush(list);
                list=new ArrayList();
2.              afterWrite.set(0);
                preWrite.set(0);
                }
            preWrite.incrementAndGet();
            }
            list.write(value);
            afterWrite..incrementAndGet();
        }
    }

始终让after小于pre 结果发现还会卡在while里面,并且after是11 这里有个漏洞if就完事了,有点

    if(a>10)
        wait();

的感觉,唤醒后一定要check 所以最终改为

    private AtomicInteger preWrite = new AtomicInteger();
    private AtomicInteger afterWrite = new AtomicInteger();
    int batch = 10000;
    Queue<Integer> list = new ConcurrentLinkedQueue<>();

    void put(int value) throws InterruptedException {
        int count = preWrite.incrementAndGet();
        while (count > batch) {
            synchronized (this) {
                if (preWrite.get() > batch) {
                    while (afterWrite.get() != batch)
                        Thread.sleep(0);
                    list = new ConcurrentLinkedQueue();
                    afterWrite.set(0);
                    preWrite.set(0);
                }
                count = preWrite.incrementAndGet();
            }
        }
        list.add(value);
        afterWrite.incrementAndGet();
    }

ConcurrentLinkedQueue这个数据结构使用有问题

   int[] list = new int[batch];
    static final int testCount = 1000000;
    

    void put(int value) throws InterruptedException {
        int count = preWrite.incrementAndGet();
        while (count > batch) {
            synchronized (this) {
                if (preWrite.get() > batch) {
                    while (afterWrite.get() != batch)
                        Thread.sleep(0);
                    list = new int[batch];
                    afterWrite.set(0);
                    preWrite.set(0);
                }
                count = preWrite.incrementAndGet();
            }
        }
        list[count - 1] = value;
        afterWrite.incrementAndGet();
    }

因为count预先占位了