Safe publication of local final references
Asked Answered
E

3

10

I know that you can safely publish a non-thread safe object by writing the reference to a final or volatile field which will later be read by exactly one other thread, provided that upon publication, the thread that created the object discards the reference to it so it can no longer interfere with or unsafely observe the object's use in the other thread.

But in this example, there's no explicit final field, only final local variables. If the caller discards its reference to unsafe, is this safe publication?

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

I found a few Q&As, like this one, suggesting that final local variables are implicitly "copied" into anonymous classes. Does that mean that the above example is equivalent to this?

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

Edit for clarification:

Unsafe could be anything, but say it's something like this:

public class Unsafe {
    public int x;
}

And mExecutor is anything that satisfies the contract of Executor.

Evanthe answered 7/7, 2016 at 17:43 Comment(4)
Your executor is using a thread safe queue. To see this issue you have to pass the object between threads without using appropriate memory barriers anywhere.Solidus
@PeterLawrey class Executor { void execute(Runnable r) { } } - no queue here. But the point is probably valid nevertheless...Hoboken
BTW Unsafe is a class which has a singleton, though you can create more of them...Solidus
@PeterLawrey I'm not referring to sun.misc.Unsafe. Edited for clarity.Evanthe
H
5

Although, admittedly, I'm not entirely sure that I got the actual point of your question, and (as pointed out in the comments) the issue is likely not really an issue in your particular case, maybe the relevant insights can be gained from a test/example

Considering the following class:

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);
            }
        });
    }
}

One can compile it, and obtain two .class files:

  • SafePublication.class
  • SafePublication$1.class for the inner class

Decompiling the class file for the inner class yields the following:

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
}

One can see that for the final parameter, there is indeed a field introduced in this class. This field is val$unsafe, and it is a final field in the class file sense, and it is initialized in the constructor.

(This is not entirely equivalent to the second code snippet that you posted, because the second one contains two final fields, and they are both initialized with the same value. But regarding the issue of safe publication, the effect should be the same).

Hoboken answered 7/7, 2016 at 20:35 Comment(1)
This is exactly what I was getting at.Evanthe
D
3

You left some important code out of your first example: The mExecutor object probably owns a BlockingQueue. The mExecutor.execute(r) call probably calls q.put(r) to add your task to the queue, and then sometime later, the worker thread calls r=q.take() to obtain the task before it can call r.run().

The put() and take() methods of the blocking queue will establish the same kind of "happens before" relationship between events in the two threads that would be established by one of the "safe publication" idioms.

Whatever the first thread updates in memory before calling q.put(r) is guaranteed to become visible to the second before the q.take() call returns.

Donothingism answered 7/7, 2016 at 18:12 Comment(7)
The argument you make I think misses the point here: the OP is explicitly talking about non thread-safe objects, the „happens before“ semantics helps set the reference but not against mutating a mutable object by the caller after the Runnable has been put into the queue and possible exactly at the same time as it is being used in the Runnable. So in fact it guarantees exactly nothing in terms of thread safety and safe publication in regards to accessing the local variable in question.Russelrussell
@TomaszStanczak, OP seemed to say that would not happen, "...provided that upon publication, the thread that created the object discards the reference to it so it can no longer interfere with or unsafely observe the object's use in the other thread."Donothingism
And exactly this is the guarantee and not the "happens before" relationship of put and get, that per itself cannot give this guarantee. Actually already the function call has this relationship, what happens is: 1. call publish, 2. put, 3. get. If the original variable reference gets discarded the call of publish is enough regardless of what happens next.Russelrussell
@jameslarge The key is passing the reference in some manner that creates a memory barrier, so that values previously written to unsynchronized fields in the publishing thread are guaranteed to be visible to the receiving thread. Discarding the reference has nothing to do with that, per se; it just avoids subsequent unsafe use in the publishing thread. I think you're correct that a BlockingQueue would provide this guarantee, and any practical Executor probably does have a BlockingQueue or something like it, but I'm not sure about that.Evanthe
@jameslarge it was me that obviously had missed the point :-) - discarding the reference is not questioned and memory barrier was asked about and not the other way round...Russelrussell
I am still not quite happy with BlockingQueue, it seems to not use synchronized keyword so cannot be a sufficient memory barrier for the Unsafe's unsynchronized fields.Russelrussell
The BlockingQueue is perfectly safe. Using synchronized isn't the end all be all for thread safety. The LinkedBlockingQueue uses a Lock which is equivalent to synchronized.Rum
R
1

The question seems to be partly answered by this answer:

Java multi-threading & Safe Publication

at least regarding "safe publication".

Now if the caller discards its reference the variable will be safe, because there exist no reference to the variable except in the final local variable.

And regarding the code examples - in my eyes both code snippets are equivalent. Introducing an additional local variable doesn't change the semantics, in both cases the compiler recognizes the reference as immutable and let you go with it.

EDIT - I am leaving this part to document my misinterpretation of the OP's question

To clarify - I take usage of final or volatile as granted in this example, so the proper memory barrier catering for the visibility of the object reference is there, the only point is the possible mutability of the non thread-safe object which cannot be guaranteed using memory barriers and actually has nothing to do with them. It can be taken care of either through proper synchronization or leaving only one reference to the content.

EDIT2 – after reading the OP’s comments

I have just looked at the JSR 133 FAQ - AFAIU a safe publication of a reference to object using a memory barrier doesn't guarantee that the unsychronized fields of the mentioned referenced object are visible too. Neither by final nor volatile.

If I am not misinterpreting this FAQ only synchronizing on the same monitor defines a “happens-before” relationship for all the writes one thread did before releasing the synchronizing lock and acquiring the lock on the same monitor by another thread.

I may be mistaken but it sounds to me as if also non-synchronized fields of the referenced object would be visible.

If using final keyword (like in your example where the parameter gets inserted as a final field) - only instance fields of the referenced object that themselves are final are guaranteed to be visible after the construction of the object ends.

But in the BlockingQueue (and as its implementation the LinkedBlockingQueue) I see no synchronized keyword whatsoever – it seems that it uses some very clever code to implement synchronization by using volatile fields, to me it doesn’t sound like a synchronization on a monitor in the sense described in JSR 133.

Which would mean that common blocking queues used by Executor do not guarantee visibility of non-final fields of your Unsafe instances. While the reference itself can be safely published using just the final keyword, the safe publishing of the fields this reference points to requires either the fields to be final, too, or a synchronization with a monitor shared by the writer and reader.

Don’t shoot the messenger :-).

Russelrussell answered 7/7, 2016 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.