Безопасная публикация местных окончательных ссылок

Я знаю, что вы можете безопасно опубликовать не потокобезопасный объект, написав ссылку на final или же volatile поле, которое позже будет прочитано ровно одним другим потоком, при условии, что после публикации поток, создавший объект, отбрасывает ссылку на него, так что он больше не может мешать или небезопасно наблюдать за использованием объекта в другом потоке.

Но в этом примере нет явного final только поле final локальные переменные. Если вызывающий отказывается от ссылки на unsafe это безопасная публикация?

void publish(final Unsafe unsafe) {
    mExecutor.execute(new Runnable() {
        public void run() {
            // do something with unsafe
        }
    }
}

Я нашел несколько вопросов и ответов, как этот, предполагая, что final локальные переменные неявно "копируются" в анонимные классы. Означает ли это, что приведенный выше пример эквивалентен этому?

void publish(final Unsafe unsafe) {
    mExecutor.execute(new Runnable() {
        final Unsafe mUnsafe = unsafe;
        public void run() {
            // do something with mUnsafe
        }
    }
}

Изменить для уточнения:

Unsafe может быть что угодно, но скажите что-то вроде этого:

public class Unsafe {
    public int x;
}

А также mExecutor это все, что удовлетворяет договору Executor,

3 ответа

Решение

Хотя, по общему признанию, я не совсем уверен, что я правильно понял ваш вопрос, и (как указано в комментариях) проблема, скорее всего, не является проблемой в вашем конкретном случае, может быть, соответствующие выводы можно получить из тест / пример

Учитывая следующий класс:

import java.util.concurrent.ExecutorService;

class Unsafe
{

}

class SafePublication
{
    private final ExecutorService mExecutor = null;

    public void publish(final Unsafe unsafe)
    {
        mExecutor.execute(new Runnable()
        {
            @Override
            public void run()
            {
                // do something with unsafe
                System.out.println(unsafe);
            }
        });
    }
}

Можно скомпилировать и получить два .class файлы:

  • SafePublication.class
  • SafePublication$1.class для внутреннего класса

Декомпиляция файла класса для внутреннего класса дает следующее:

class SafePublication$1 implements java.lang.Runnable {
  final Unsafe val$unsafe;

  final SafePublication this$0;

  SafePublication$1(SafePublication, Unsafe);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LSafePublication;
       5: aload_0
       6: aload_2
       7: putfield      #2                  // Field val$unsafe:LUnsafe;
      10: aload_0
      11: invokespecial #3                  // Method java/lang/Object."<init>":()V
      14: return

  public void run();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #2                  // Field val$unsafe:LUnsafe;
       7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      10: return
}

Это видно по final параметр, действительно есть поле, введенное в этом классе. Это поле val$unsafe, и это последнее поле в смысле файла класса, и оно инициализируется в конструкторе.

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

Вы оставили важный код из своего первого примера: mExecutor объект, вероятно, владеет BlockingQueue, mExecutor.execute(r) позвонить, вероятно, звонит q.put(r) чтобы добавить свою задачу в очередь, а затем через некоторое время рабочий поток вызывает r=q.take() получить задание до того, как оно сможет вызвать r.run(),

put() а также take() методы очереди блокировки устанавливают тот же тип отношения "происходит раньше" между событиями в двух потоках, который будет установлен одним из идиом "безопасной публикации".

Все, что первый поток обновляется в памяти перед вызовом q.put(r) гарантированно станет видимым для второго до q.take() звонок возвращается.

На этот вопрос, похоже, частично отвечает этот ответ:

Многопоточность Java и безопасная публикация

по крайней мере, в отношении "безопасной публикации".

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

Что касается примеров кода - на мой взгляд, оба фрагмента кода эквивалентны. Введение дополнительной локальной переменной не меняет семантику, в обоих случаях компилятор распознает ссылку как неизменную и позволяет вам использовать ее.

РЕДАКТИРОВАТЬ - я оставляю эту часть, чтобы задокументировать мое неправильное толкование вопроса ОП

Чтобы уточнить - я беру использование final или же volatile как указано в этом примере, поэтому существует надлежащий барьер памяти, обеспечивающий видимость ссылки на объект, единственная точка - возможная изменчивость не поточно-ориентированного объекта, которая не может быть гарантирована с помощью барьеров памяти и фактически не имеет ничего общего с их. Об этом можно позаботиться либо путем правильной синхронизации, либо оставив только одну ссылку на контент.

EDIT2 - после прочтения комментариев ОП

Я только что посмотрел на JSR 133 FAQ - AFAIU безопасная публикация ссылки на объект с использованием барьера памяти не гарантирует, что несинхронизированные поля упомянутого ссылочного объекта также видны. Ни кем final ни volatile,

Если я не неверно истолковываю этот FAQ, то только синхронизация на одном мониторе определяет отношение "происходит до" для всех операций записи, выполненных одним потоком до снятия блокировки синхронизации и получения блокировки на том же мониторе другим потоком.

Я могу ошибаться, но для меня это звучит так, как будто бы несинхронизированные поля указанного объекта были бы видимы.

При использовании final ключевое слово (как в вашем примере, где параметр вставляется как final field) - только поля экземпляра ссылочного объекта, которые сами final гарантированно будут видны после окончания строительства объекта.

Но в BlockingQueue (и в качестве его реализации LinkedBlockingQueue) Я не вижу synchronized ключевое слово вообще - кажется, что он использует очень умный код для реализации синхронизации с помощью volatile для меня это не похоже на синхронизацию на мониторе в смысле, описанном в JSR 133.

Что означало бы, что общие очереди блокировки, используемые Executor, не гарантируют видимость неоконечных полей вашего Unsafe экземпляров. Хотя сама ссылка может быть безопасно опубликована с использованием только final Ключевое слово, для безопасной публикации полей, на которые указывает эта ссылка, требуется, чтобы поля были finalтоже или синхронизация с монитором, совместно используемая писателем и читателем.

Не стреляйте в курьера:-).

Другие вопросы по тегам