简体   繁体   中英

Thread safety in java multithreading

I found code about thread safety but it doesn't have any explanation from the person who gave the example. I would like to understand why if I don't set the "synchronized" variable before "count" that the count value will be non-atomic ( always =200 is the desired result). Thanks

    public class Example {

     private static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    for (int i = 0; i < 100; i++) {
                       //add synchronized
                        synchronized (Example.class){
                        count++;
                    }
                }
            }).start();
        }

        try{
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(count);
    }
      }

++ is not atomic

The count++ operation is not atomic. That means it is not a single solitary operation. The ++ is actually three operations: load, increment, store.

First the value stored in the variable is loaded (copied) into a register in the CPU core.

Second, that value in the core's register is incremented.

Third and last, the new incremented value is written (copied) from the core's register back to the variable's content in memory. The core's register is then free to be assigned other values for other work.

It is entirely possible for two or more threads to read the same value for the variable, say 42 . Each of those threads would then proceed to increment the value to the same new value 43 . They would then each write back 43 to that same variable, unwittingly storing 43 again and again repeatedly.

Adding synchronized eliminates this race condition. When the first thread gets the lock, the second and third threads must wait. So the first thread is guaranteed to be able to read, increment, and write the new value alone, going from 42 to 43. Once completed, the method exits, thereby releasing the lock. The second thread vying for the lock gets the go-ahead, acquiring the lock, and is able to read, increment, and write the new value 44 without interference. And so on, thread-safe.

Another problem: Visibility

However, this code is still broken.

This code has a visibility problem, with various threads possibly reading stale values kept in caches. But that is another topic. Search to learn more about volatile keyword, the AtomicInteger class, and the Java Memory Model.

I would like to understand why if I don't set the "synchronized" variable before "count" that the count value will be non-atomic.

The short answer: Because the JLS says so!

If you don't use synchronized (or volatile or something similar) then the Java Language Specification (JLS) does not guarantee that the main thread will see the values written to count by the child thread.

This is specified in great detail in the Java Memory Model section of the JLS. But the specification is very technical.

The simplified version is that a read of a variable is not guaranteed to see the value written by a preceding write if there is not a happens before (HB) relationship connecting the write and the read. Then there are a bunch of rules that say when an HB relationship exists. One of the rules is that there is an HB between on thread releasing a mutex and a different thread acquiring it.

An alternative intuitive (but incomplete and technically inaccurate) explanation is that the latest value of count may be cached in a register or a chipset's memory caches. The synchronized construct flushes values to be memory.

The reason that is an inaccurate explanation is that JLS doesn't say anything about registers, caches and so on. Rather, the memory visibility guarantees that the JLS specifies are typically implemented by a Java compiler inserting instructions to write registers to memory, flush caches, or whatever is required by the hardware platform .


The other thing to note is that this is not really about count++ being atomic or not 1 . It is about whether the result of a change to count is visible to a different thread.

1 - It isn't atomic, but you would get the same effect for an atomic operation like a simple assignment!

Let's get back to the basics with a Wall Street example.

Let's say, You (Lets call T1 ) and your friend (Lets call T2) decided to meet at a coffee house on Wall Street. You both started at same time, let's say from southern end of the Wall Street (Though you are not walking together). You are waking on one side of footpath and your friend is walking on other side of the footpath on Wall Street and you both going towards North (Direction is same).

Now, let's say you came in front of a coffee house and you thought this is the coffee house you and your friend decided to meet, so you stepped inside the coffee house, ordered a cold coffee and started sipping it while waiting.

But, On other side of the road, similar incident happened, your friend came across a coffee shop and ordered a hot chocolate and was waiting for you.

After a while, you both decided the other one is not going to come dropped the plan for meeting.

You both missed your destination and time. Why was this happened? Don't have to mention but, Because you did not decided the exact venue.

The code

synchronized(Example.class){
 counter++;
}

solves the problem that you and your friend just encountered.

In technical terms the operation counter++ is actually conducted in three steps;

Step 1: Read the value of counter (lets say 1)
Step 2: Add 1 in to the value of counter variable.
Step 3: Write the value of the variable counter back to memory.

If two threads are working simultaneously on counter variable, final value of the counter will be uncertain. For example, Thread1 could read the value of the counter as 1, at the same time thread2 could read the value of variable as 1. The both threads endup incrementing the value of counter to 2. This is called race condition.

To avoid this issue, the operation counter++ has to be atomic. To make it atomic you need to synchronize execution of the thread. Each thread should modify the counter in organized manner.

I suggest you to read book Java Concurrency In Practice, every developer should read this book.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM