Volatile piggyback. Is this enough for visiblity?

不问归期 提交于 2019-11-27 12:07:14
Adam Zalcman

Java Memory Model defines the happens-before relationship which has the following properties (amongst others):

  • "Each action in a thread happens-before every action in that thread that comes later in the program order" (program order rule)
  • "A write to a volatile field happens-before every subsequent read of that same volatile" (volatile variable rule)

These two properties together with transitivity of the happens-before relationship imply the visibility guarantees that OP seeks in the following manner:

  1. A write to a in thread 1 happens-before a write to sync in a call to sync() in thread 1 (program order rule).
  2. The write to sync in the call to sync() in thread 1 happens-before a read to sync in a call to sync in thread 2 (volatile variable rule).
  3. The read from sync in the call to sync() in thread 2 happens-before a read from a in thread 2 (program order rule).

This implies that the answer to the question is yes, i.e. the call to sync() in each iteration in threads 1 and 2 ensures visibility of changes to a, b and c to the other thread(s). Note that this ensures visibility only. No mutual exclusion guarantees exist and hence all invariants binding a, b and c may be violated.

See also Java theory and practice: Fixing the Java Memory Model, Part 2. In particular the section "New guarantees for volatile" which says

Under the new memory model, when thread A writes to a volatile variable V, and thread B reads from V, any variable values that were visible to A at the time that V was written are guaranteed now to be visible to B.

Incrementing a value between threads is never thread-safe with just volatile. This only ensures that each thread gets an up to date value, not that the increment is atomic, because at the assembler level your ++ is actually several instructions that can be interleaved.

You should use AtomicInteger for a fast atomic increment.

Edit: Reading again what you need is actually a memory fence. Java has no memory fence instruction, but you can use a lock for the memory fence "side-effect". In that case declare the sync method synchronized to introduce an implicit fence:

void synchronized sync() {
    sync++;
}

From javadoc:

An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.

A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.

So I think that writing to volatile var is not an equivalent to syncronization in this case and it doesn't guarantee happens-before order and visibility of changes in Thread1 to Thread2

The pattern is usually like this.

public void setup() {
    a = 2;
    b = 3;
    c = 4;
    sync();
}

However, while this guarantees the other threads will see this change, the other threads can see an incomplete change. e.g. the Thread2 might see a = 2, b = 3, c = 0. or even possibly a = 2, b = 0, c = 4;

Using the sync() on the reader doesn't help much.

You don't really have to manually synchronize at all, just use an automatically synchronized data structure, like java.util.concurrent.atomic.AtomicInteger.

You could alternatively make the sync() method synchronized.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!