Is it possible to modify a non-volatile variable s

2019-03-30 11:59发布

问题:

I have a Thread-X which reads a non-volatile variable every second, doing so without any means of synchronization.

Now I was wondering is there some way to modify that non-volatile variable on Thread-Y such that Thread-Y's write would be (eventually) visible on Thread-X ?

public class Test {
    private static boolean double = 1; // this variable is
                                             // strictly not volatile

    public static void main(String args[]) {
        new java.lang.Thread(new java.lang.Runnable() {

            @Override
            public void run() {
                while (true) {
                    System.out.println(variable);
                    try {
                        java.lang.Thread.currentThread().sleep(1000);
                    } catch (java.lang.InterruptedException e) {
                    }
                }
            }
        }).start(); // line 17
        new java.lang.Thread(new java.lang.Runnable() {

            @Override
            public void run() {
                // the task is to change variable to "2" such the write
                // is (eventually) registered by the other threads

                // allowed to use any synchronization/locking techniques
                // required, but line 1 to line 17 must not be changed
            }
        }).start();
    }
}

Is it possible to modify a non-volatile variable such that another thread which reads it without any synchronization techniques (raw read) is able to "see" the update eventually?

Background:

I need to read a variable from a large number of threads, for an infinite amount of times.

From what I understand (correct me if I'm wrong), on most cpus (e.g. x86) reads of volatile variables are "almost totally free" but not "totally free".

Now since I have an infinite number of reads from an infinite number of threads, I would love the variable to be non-volatile. However, once in a blue moon the variable needs to be updated. In my use-case, it really doesn't matter how expensive it takes to update that variable, but that update must eventually be readable by the reader-threads.

Solutions:

Based on Tomasz's comment, I've built this solution and I was wondering is Solution-1 flawed or is it solid?

public class Solution1 {
    private static double variable = 1; // this variable is
                                        // strictly not volatile

    public static void main(String args[]) {
        new java.lang.Thread(new java.lang.Runnable() {

            @Override
            public void run() {
                while (true) {
                    System.out.println(variable);
                    try {
                        java.lang.Thread.currentThread().sleep(1000);
                    } catch (java.lang.InterruptedException e) {
                    }
                }
            }
        }).start(); // line 17
        new java.lang.Thread(new java.lang.Runnable() {

            @Override
            public void run() {
                variable = 2;
                // writer-thread now terminates,
                // is it guaranteed that when it
                // "terminates successfully", variable
                // is updated on the reader-thread ?
            }
        }).start();
    }
}

Based on Joonas's comment, I've built this solution and I was wondering is Solution-2 flawed or is it solid?

public class Solution2 {
    private static double variable = 1; // this variable is
                                        // strictly not volatile
    private static volatile boolean lock = false;

    public static void main(String args[]) {
        new java.lang.Thread(new java.lang.Runnable() {

            @Override
            public void run() {
                while (true) {
                    System.out.println(variable);
                    try {
                        java.lang.Thread.currentThread().sleep(1000);
                    } catch (java.lang.InterruptedException e) {
                    }
                }
            }
        }).start(); // line 17
        new java.lang.Thread(new java.lang.Runnable() {

            @Override
            public void run() {
                variable = 2;
                lock = false; // does this line guarantee
                                // that other threads will now 
                                // see the update to variable (piggypacking)?

                // now let's assume this thread doesn't terminate
            }
        }).start();
    }
}

回答1:

Is it possible to modify a non-volatile variable such that another thread which reads it without any synchronization techniques (raw read) is able to "see" the update eventually?

No. Some synchronization technique must be used, because otherwise the (JIT) compiler is allowed to optimize your line to System.out.println(false); (if false is the value first seen by that thread). That is, it can optimize reading the variable away.

I have no idea how likely it would actually do that, but it's okay according to the Java Memory Model, so your options are:

  • volatile. This is probably the simplest and lightest option for your case.
  • AtomicBoolean. Some discussion of it vs. volatile here and here.
  • synchronized block. Overkill in this case.
  • java.util.concurrent.locks.Lock. More features than in synchronized.
  • Thread.join(). Not useful in this case (the reading thread would wait for the writer to terminate).
  • Piggybacking. Don't even think about it. Too many things that may and will go wrong.

Just use volatile and let the JVM worry about implementing it efficiently. It's not expensive.



回答2:

Quoting Synchronization and the Java Memory Model from Concurrent Programming in Java by Doug Lea:

Changes to fields made by one thread are guaranteed to be visible to other threads only under the following conditions:

  • A writing thread releases a synchronization lock and a reading thread subsequently acquires that same synchronization lock.

  • If a field is declared as volatile, any value written to it is flushed and made visible by the writer thread before the writer thread performs any further memory operation (i.e., for the purposes at hand it is flushed immediately). Reader threads must reload the values of volatile fields upon each access.

  • The first time a thread accesses a field of an object, it sees either the initial value of the field or a value since written by some other thread.

  • As a thread terminates, all written variables are flushed to main memory. For example, if one thread synchronizes on the termination of another thread using Thread.join, then it is guaranteed to see the effects made by that thread (see §4.3.2).

Last two options do not apply to your situations so you need either volatile or synchronized, sorry. Note that AtomicInteger.get() simply returns volatile value, so you get nothing except extra layer.



回答3:

I'm trying to get "totally free" reads, and I'm prepared to do a very expensive write in exchange for "totally free" reads. Is it true that for this problem, there is no better solution than declaring it as volatile?

Nothing is totally free. A read of a volatile variable which is not being changed can be sub-nanosecond which may be more than fast enough. If you read after a write it can take 5 nano-seconds. The branch could take 5 - 10 nanoseconds regardless of your "free" read and if you do something as simple as taking the time e.g. with System.nanoTime(), this can take 20 - 180 nano-seconds depending on your OS. The cost your read should be VERY low in your list of concerns.

For example, you should be worried about whether your code is warmed up so it is compiled instead of interpreted. This can make far, far more difference (see below)


volatile can be needed in a number of situations, but I don't believe you have one of them.

The most common situation is as @Joonas Pulakka mentions where the JIT optimises the field in a manner you don't want i.e. it stops reading the value and makes it a local variable, because its not volatile.

This optimisation can happen after a loop iterates 10,000 times in quick succession. In your case, it is not quick succession, but over 2.7 hours so the JIT might never optimise the code.