Потокобезопасный ArrayList

135
13 февраля 2021, 11:00

Пишу свою реализацию потокобезопасного эррэй листа на CAS-блокировках (не спрашивайте зачем). Столкнулся с некоторым непонятным мне поведением в процессе записи элементов в лист. Код реализации методов записи:

public class CustomArrayList<T> implements List<T> {
private static final int DEFAULT_LENGTH = 16;
private volatile T[] array;
private AtomicInteger size = new AtomicInteger(0);
private volatile int rebuildSize = DEFAULT_LENGTH - 1;
public CustomArrayList() {
    array = (T[]) new Object[DEFAULT_LENGTH];
}
public boolean add(T t) {
    if (checkSize()) {
        int tSize = size.getAndIncrement();
        if (tSize >= rebuildSize) {
            rebuild(tSize);
        }
        array[tSize] = t;
        return true;
    }
    return false;
}
public boolean addAll(Collection<? extends T> c) {
    if (checkSize()) {
        int tSize;
        do{
            tSize = size.get();
        }
        while(!size.compareAndSet(tSize, tSize + c.size()));
        if (tSize + c.size() >= rebuildSize) {
            rebuild(tSize + c.size());
        }
        System.arraycopy((T[]) c.toArray(), 0, array, tSize, c.size());
        return true;
    }
    return false;
}
private void rebuild(int newSize) {
    synchronized (array) {
        synchronized (size) {
            if(newSize >= rebuildSize) {
                long tempSize = size.get() * 2;
                tempSize = tempSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : tempSize;
                T[] newArray = (T[]) new Object[(int) tempSize];
                System.arraycopy(array, 0, newArray, 0, array.length);
                array = newArray;
                rebuildSize = array.length - 1;
            }
        }
    }
}
private boolean checkSize() {
    return size.get() < Integer.MAX_VALUE;
}

}

Код, который пишет в лист:

private static CountDownLatch cdl;
private static List<String> list;
private static final int ITER_COUNT = 6;

public static void main(String[] args) throws InterruptedException {
    List<String> aList = Stream.of("A", "B", 
"C").collect(Collectors.toList());
    List<String> bList = Stream.of("D", "E", 
"F").collect(Collectors.toList());
    List<String> cList = Stream.of("G", "H", 
"I").collect(Collectors.toList());
    List<String> dList = Stream.of("J", "K", 
"L").collect(Collectors.toList());
for (int j = 0; j < 1000; j++) {
    list = new CustomArrayList<>(10);
    cdl = new CountDownLatch(1);
    new Thread(() -> {
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < ITER_COUNT; i++) {
            if (i % 3 == 0 && i > 0) {
                list.addAll(aList);
            } else {
                list.add(String.valueOf(i));
            }
        }
    }).start();
    new Thread(() -> {
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < ITER_COUNT; i++) {
            if (i % 3 == 0 && i > 0) {
                list.addAll(bList);
            } else {
                list.add(String.valueOf(i));
            }
        }
    }).start();
    new Thread(() -> {
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < ITER_COUNT; i++) {
            if (i % 3 == 0 && i > 0) {
                list.addAll(cList);
            } else {
                list.add(String.valueOf(i));
            }
        }
    }).start();
    new Thread(() -> {
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < ITER_COUNT; i++) {
            if (i % 3 == 0 && i > 0) {
                list.addAll(dList);
            } else {
                list.add(String.valueOf(i));
            }
        }
    }).start();
    cdl.countDown();
    Thread.sleep(50);
    System.out.println(list);
}

}

Фрагмент вывода в консоль:

0,1,2,G,H,I,4,5,0,1,0,2,A,B,C,4,5,1,2,D,E,F,4,5,0,1,2,J,K,L,4,5 0,1,2,G,H,I,4,5,0,1,0,2,A,B,C,4,5,1,2,D,E,F,4,5,0,1,2,J,K,L,4,5 0,1,2,0,1,2,A,B,C,0,G,H,I,1,2,null,null,null,0,1,2,J,K,L,4,5,4,5,4,5,4,5 0,1,2,A,B,C,4,5,0,1,2,D,E,F,4,5,0,1,2,G,H,I,4,5,0,1,2,J,K,L,4,5

Мне не приходит в голову, почему в некоторых случаях в листе появляются значения null. При единичном добавлении элементов такое поведение тоже имеет место, только гораздо реже. Буду признателен, если кто нибудь подскажет, в чем дело. Спасибо.

Answer 1

System.arraycopy не является потокобезопасным. Он у Вас вызывается под гонкой. Вот ссылка на хороший пример, который демонстрирует проблемы System.arraycopy: http://www.programmersought.com/article/7454319322/

READ ALSO
Несколько яндекс карт в цикле с кнопками. Не работают кнопки

Несколько яндекс карт в цикле с кнопками. Не работают кнопки

Есть такая проблемкаВыводится какое-то кол-во яндекс карт, сейчас 3, в цикле

99
Как сравнить разницу между текущей и будущей датой с точностью до месяца?

Как сравнить разницу между текущей и будущей датой с точностью до месяца?

Есть задачка, в которой необходимо сравнить текущую дату с заданной в будущем с точностью до месяца включительно и определить разницу в количестве...

117
Ошибка CORS при работе с API

Ошибка CORS при работе с API

У Pinboard есть APIКогда я хочу обратится к нему из браузера мне выдает ошибку CORS

121
Slick slider + Thumbnails в модальном окне

Slick slider + Thumbnails в модальном окне

Планирую использовать slick slider в модальном окне (плагин Remodal)Посмотреть можно тут в разделе "Наши работы"

139