Элементы массива Java и вопрос видимости памяти - PullRequest
1 голос
/ 20 сентября 2019

Я прочитал несколько вопросов и ответов о видимости элементов массива Java из нескольких потоков, но до сих пор не могу разобраться в некоторых случаях.Чтобы продемонстрировать, с чем у меня проблемы, я придумал простой сценарий: предположим, что у меня есть простая коллекция, которая добавляет элементы в одно из своих n блоков, хешируя их в одно (Bucket - это своего рода список),И каждое ведро синхронизируется отдельно.Например:

private final Object[] locks = new Object[10];
private final Bucket[] buckets = new Bucket[10];

Здесь ведро i должно охраняться lock[i].Вот как выглядит код добавления элементов:

public void add(Object element) {
        int bucketNum = calculateBucket(element); //hashes element into a bucket
        synchronized (locks[bucketNum]) {
            buckets[bucketNum].add(element);
        }
    }

Поскольку 'buckets' является окончательным, у него не будет проблем с видимостью даже без синхронизации.Я предполагаю, что с синхронизацией, это не будет иметь никаких проблем с видимостью без финала, это правильно?

И, наконец, немного более хитрая часть.Предположим, я хочу скопировать и объединить содержимое всех блоков и очистить всю структуру данных из произвольного потока, например, так:

public List<Bucket> clear() {
    List<Bucket> allBuckets = new List<>();
    for(int bucketNum = 0; bucketNum < buckets.length; bucketNum++) {
        synchronized (locks[bucketNum]) {
            allBuckets.add(buckets[bucketNum]);
            buckets[bucketNum] = new Bucket();
        }    
    }
    return allBuckets;
}

В основном я заменяю старый блок новым и возвращаюСтарый.Этот случай отличается от add(), потому что мы не изменяем объект, на который ссылается ссылка в массиве, но мы напрямую изменяем массив / ссылку.

Обратите внимание, что мне все равно, если сегмент 2измененный, пока я держу блокировку для сегмента 1, мне не нужно, чтобы структура была полностью синхронизирована и согласована, достаточно лишь видимости и почти согласованности.

Таким образом, предполагая, что каждый bucket[i] только когда-либо изменяетсяпод lock[i], вы бы сказали, что этот код работает?Я надеюсь, что смогу понять, почему и почему нет, и лучше понять, спасибо.

Ответы [ 2 ]

2 голосов
/ 20 сентября 2019

Первый вопрос.

Безопасность потока в этом случае зависит от того, правильно ли передана ссылка на объект, содержащий locks и buckets (назовем его Container),

Только представьте: один поток занят созданием нового объекта Container (выделение памяти, создание экземпляров массивов и т. Д.), В то время как другой поток начинает использовать этот объект с половиной экземпляров, где locks и bucketsвсе еще null (они еще не были созданы первым потоком).В этом случае этот код:

    synchronized (locks[bucketNum]) {

ломается и выдает NullPointerException.Ключевое слово final предотвращает это и гарантирует, что к тому времени, когда ссылка на Container не станет нулевой, его окончательные поля уже будут инициализированы:

Объект считается полностью инициализированным, когда егоконструктор заканчиваетПоток, который может видеть ссылку на объект только после того, как этот объект был полностью инициализирован, гарантированно увидит правильно инициализированные значения для конечных полей этого объекта.( JLS 17,5 )

Второй вопрос.

Предполагая, что поля locks и buckets являются окончательными и вас не волнует согласованность всего массива и"каждый сегмент [i] только когда-либо изменяется под блокировкой [i]", этот код в порядке.

0 голосов
/ 21 сентября 2019

Просто добавьте к ответу Павла:

В своем первом вопросе вы задаете

Поскольку 'buckets' является окончательным, у него не будет проблем с видимостью даже без синхронизации.Я полагаю, что с синхронизацией не было бы проблем с видимостью без финала, верно?

Я не уверен, что вы подразумеваете под «проблемами видимости», но точнобез synchronized этот код был бы неправильным, если бы несколько потоков обращались к buckets[i], причем один из них модифицировал его (например, записывал в него).Не было бы никакой гарантии, что то, что написал один поток, станет видимым для другого.Это также включает в себя внутренние структуры сегмента, которые могут быть изменены вызовом add.

Помните, что final на buckets относятся только к одной ссылке на сам массив, а не на его ячейки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...