3

I have a String and ThreadPoolExecutor that changes the value of this String. Just check out my sample:

String str_example = "";     
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 30, (long)10, TimeUnit.SECONDS, runnables);
    for (int i = 0; i < 80; i++){
        poolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (Math.random() * 1000));
                    String temp = str_example + "1";
                    str_example = temp;
                    System.out.println(str_example);

                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        });
    }

so after executing this, i get something like that:

1
11
111
1111
11111
.......

So question is: i just expect the result like this if my String object has volatile modifier. But i have the same result with this modifier and without.

HeyAlex
  • 1,666
  • 1
  • 13
  • 31
  • 1
    Maybe you got lucky. – Kayaman Jun 21 '17 at 21:56
  • i just check it out a lot....i m trying to understand a thread caching, but that example, it's something like a magic for me. – HeyAlex Jun 21 '17 at 21:58
  • 1
    It *is* magic. That's why you can't draw conclusions from a snippet like this. Adding or removing lines of code can result in different behaviour when you write thread unsafe code. – Kayaman Jun 21 '17 at 22:01
  • 1
    https://stackoverflow.com/questions/21583448/what-can-force-a-non-volatile-variable-to-be-refreshed – AndyN Jun 21 '17 at 22:19

4 Answers4

4

There are several reasons why you see "correct" execution.

First, CPU designers do as much as they can so that our programs run correctly even in presence of data races. Cache coherence deals with cache lines and tries to minimize possible conflicts. For example, only one CPU can write to a cache line at some point of time. After write was done other CPUs should request that cache line to be able to write to it. Not to say x86 architecture(most probable which you use) is very strict comparing to others.

Second, your program is slow and threads sleep for some random period of time. So they do almost all the work at different points of time.

How to achieve inconsistent behavior? Try something with for loop without any sleep. In that case field value most probably will be cached in CPU registers and some updates will not be visible.

P.S. Updates of field str_example are not atomic so you program may produce the same string values even in presense of volatile keyword.

Andrey Cheboksarov
  • 649
  • 1
  • 5
  • 9
  • [It is important to note that neither Thread.sleep nor Thread.yield have any synchronization semantics. In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep or Thread.yield, nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.3). Your statement about context switching flushing writes isn't necessarily true. – Andy Turner Jun 21 '17 at 22:44
  • @AndyTurner thanks for pointing out this. I'll update the answer – Andrey Cheboksarov Jun 21 '17 at 22:48
  • @SotiriosDelimanolis I barely read the code and thought updates are issued in a loop which is not true. Thanks for pointing out – Andrey Cheboksarov Jun 21 '17 at 22:51
  • While `Thread.sleep` has no thread consistency effects, `Math.random()` has, as it will update a global random number generator, which is thread safe and may force other memory visibility as a side effect. Also `System.out.println` is implemented thread safe in most environment, causing additional synchronization. So between these two actions with memory visibility semantics, we have one tiny read, update, write, needing very much luck to have a data race in-between… – Holger Jul 13 '17 at 13:55
1

When you talk about concepts like thread caching, you're talking about the properties of a hypothetical machine that Java might be implemented on. The logic is something like "Java permits an implementation to cache things, so it requires you to tell it when such things would break your program". That does not mean that any actual machine does anything of the sort. In reality, most machines you are likely to use have completely different kinds of optimizations that don't involve the kind of caches that you're thinking of.

Java requires you to use volatile precisely so that you don't have to worry about what kinds of absurdly complex optimizations the actual machine you're working on might or might not have. And that's a really good thing.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
1

Your code is unlikely to exhibit concurrency bugs because it executes with very low concurrency. You have 10 threads, each of which sleep on average 500 ms before doing a string concatenation. As a rough guess, String concatenation takes about 1ns per character, and because your string is only 80 characters long, this would mean that each thread spends about 80 out of 500000000 ns executing. The chance of two or more threads running at the same time is therefore vanishingly small.

If we change your program so that several threads are running concurrently all the time, we see quite different results:

static String s = "";

public static void main(String[] args) throws Exception {
    ExecutorService executor = Executors.newFixedThreadPool(5);

    for (int i = 0; i < 10_000; i ++) {
        executor.submit(() -> {
            s += "1";
        });
    }
    executor.shutdown();
    executor.awaitTermination(1, TimeUnit.MINUTES);
    System.out.println(s.length());
}

In the absence of data races, this should print 10000. On my computer, this prints about 4200, meaning over half the updates have been lost in the data race.

What if we declare s volatile? Interestingly, we still get about 4200 as a result, so data races were not prevented. That makes sense, because volatile ensures that writes are visible to other threads, but does not prevent intermediary updates, i.e. what happens is something like:

Thread 1 reads s and starts making a new String
Thread 2 reads s and starts making a new String
Thread 1 stores its result in s
Thread 2 stores its result in s, overwriting the previous result

To prevent this, you can use a plain old synchronized block:

    executor.submit(() -> {
        synchronized (Test.class) {
            s += "1";
        }
    });

And indeed, this returns 10000, as expected.

meriton
  • 68,356
  • 14
  • 108
  • 175
0

It is working because you are using Thread.sleep((long) (Math.random() * 100));So every thread has different sleep time and executing may be one by one as all other thread in sleep mode or completed execution.But though your code is working is not thread safe.Even if you use Volatile also will not make your code thread safe.Volatile only make sure visibility i.e when one thread make some changes other threads are able to see it.

In your case your operation is multi step process reading the variable,updating then writing to memory.So you required locking mechanism to make it thread safe.

gati sahu
  • 2,576
  • 2
  • 10
  • 16