Synchronized access to shared mutable data and atomic operations in java

When multiple threads share mutable data, each thread that reads or writes the data must perform synchronization. Without synchronization, there is no guarantee that changes made by one thread will be known to another thread. Failure to synchronize shared mutable data can lead to liveness failure and safety failure of the program. Such failures are the most difficult to debug. They can be intermittent, time-dependent, and the program's behavior can be radically different on different VMs. The volatile modifier is an acceptable form of synchronization if only interactive communication between threads is required, and no mutual exclusion is required, but it can take some tricks to use it correctly.

"Atomic operation (atomic operation) does not need to be synchronized", this is the old saying of Java multi-threaded programming. The so-called atomic operation refers to an operation that will not be interrupted by the thread scheduling mechanism; once this operation starts, it runs until the end without any context switch (switch[1] to another thread).

1.1 The keyword synchronized

The keyword synchronized ensures that at the same time, only one thread can execute a method or a block of code.

The following example:

package thread;

import java.util.concurrent.TimeUnit;

public class StopThreadSynch {

    private static boolean stopRequested;


    public static synchronized boolean isStopRequested() {
        return stopRequested;
    }


    public static synchronized void setStopRequested(boolean stopRequested) {
        StopThreadSynch.stopRequested = stopRequested;
    }


    public static void main(String[] args) throws InterruptedException {
        long startDate = System.currentTimeMillis();
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                int i = 0;
                while(!isStopRequested()) {
                    i++;
                    System.out.println(i);
                }

            }
        });
        thread.start();

        TimeUnit.SECONDS.sleep(1);

        setStopRequested(true);
        long endDate = System.currentTimeMillis();
        System.out.println(endDate - startDate);
        System.out.println("-----------");
    }
}

Note that both the write method and the read method are synchronized. If only the write method is synchronized and the read method is not synchronized, the synchronization will not work.

1.2 See the following example
    private static int nextNumber = 0;

    public static synchronized int generateNumber() {
        return nextNumber++;
    }

The purpose of this method is to ensure that each call returns a different value (as long as it does not exceed 2 to the power of 32), so the synchronized keyword is added to the method block to ensure that multiple calls will not be interleaved. Make sure each call sees the effects of all previous calls.

Another way is to use a part of java.util.concurrent.atomic. There are several classes we commonly use under this package:
java.util.concurrent.atomic.AtomicBoolean,
java.util.concurrent.atomic.AtomicInteger,
java.util .concurrent.atomic.AtomicIntegerArray,
java.util.concurrent.atomic.AtomicLong,
java.util.concurrent.atomic.AtomicLongArray
they do exactly what we want, and may perform better than the synchronized version :

    private static final AtomicInteger nextNumber = new AtomicInteger();

    public static int generateNumber() {
        return nextNumber.getAndIncrement();
    }

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325558921&siteId=291194637