Кэш память потоков и оперативная память

Рейтинг: 2Ответов: 2Опубликовано: 06.03.2023

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

 List<Integer> integers = new ArrayList<>();

    ListThread listThread1 = new ListThread(integers);
    ListThread listThread2 = new ListThread(integers);
    ListThread listThread3 = new ListThread(integers);
    ListThread listThread4 = new ListThread(integers);
    ListThread listThread5 = new ListThread(integers);
    ListThread listThread6 = new ListThread(integers);
    ListThread listThread7 = new ListThread(integers);

    listThread1.start();
    listThread2.start();
    listThread3.start();
    listThread4.start();
    listThread5.start();
    listThread6.start();
    listThread7.start();

    listThread1.join();
    listThread2.join();
    listThread3.join();
    listThread4.join();
    listThread5.join();
    listThread6.join();
    listThread7.join();
    
    System.out.println(integers);

Выводится следующая ошибка:

Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: Index 171 out of bounds for length 163
at java.base/java.util.ArrayList.add(ArrayList.java:455)
at java.base/java.util.ArrayList.add(ArrayList.java:467)

Вопрос: как я понял, у каждого потока свой кэш и у каждого в кэше хранится "своя версия" полей integers, а сам список integers хранится в оперативной памяти. Как тогда происходит это взаимодействие информации из кэша и информации из оперативной памяти, ведь если поток хочет положить какой-то элемент за пределы массива, то значит в кэше этого потока это массив уже увеличен. И как формируется список в нашей оперативной памяти, если в кэшах есть множество его вариантов, какой вариант попадает в оперативную память?

public class ListThread extends Thread {

private final List<Integer> list;

public ListThread(List<Integer> list) {
    this.list = list;
}

@Override
public void run() {
    for (int i = 0; i < 400; i++) {
        list.add(i);
    }
}

}

Ответы

▲ 3Принят

Тут дело в том, что потоки при выполнении своей работы конкурируют между собой, а коллекция ArrayList, как правильно заметили в другом ответе, не является потокобезопасной. Так выглядит метод add() класса ArrayList, который вы используете для добавления элементов:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

а так выглядит приватный метод add(), на который он опирается:

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

Потоки конкуррентно выполняют вызов публичного метода add(), и при такой их работе могут быть разные "неожиданные" эффекты. Может быть брошено исключение, а может быть и не брошено, зато в окончательном списке окажутся далеко не все элементы.

Разберём случай с исключением. Все потоки одновременно вызывают add(), который в свою очередь вызывает приватный add(), в котором блок if не выполняется (поначалу size меньше размера массива), элемент добавляется и переменная size наращивается. Может произойти так, что все потоки одновременно нарастят size таким образом, что она перескочит через elementData.length, превысив фактический размер массива. И при следующем вызове приватного метода add() блок if всё так же не выполнится, соответственно, массив не расширится, соответственно, будем пытаться добавить элемент по индексу, превышающему размер массива, отсюда и исключение.

Но есть и другая упомянутая выше ситуация. Потоки, вызывая add() одновременно и не ожидая друг друга, зачастую будут вызывать его с одинаковым size, так как за счёт того, что не все add()-ы отработали, она не нарастится нужное количество раз. Отсюда получаем добавление на одинаковые позиции и "неполноценный" окончательный список в том случае, если программа отработает и исключение не будет брошено.

▲ 0

ArrayList просто не потокобезопасный объект. Используйте потокобезопасные коллекции для работы со списком из нескольких потоков, варианты есть например на английском СО.

Например, можно использовать Collections.synchronizedList.