Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

1. Introduction to Volatile

Three features of concurrent programming:

1. Atomicity: Once an operation is started, it will run to the end without being interrupted by other threads. This operation can be a single operation or multiple operations.

Visibility: When one thread changes the value of a shared variable, other threads can immediately sense the change.

3, orderliness: observe in this thread, the operation is orderly; If you observe another thread in one thread, all operations are unordered. The Semantics WithIn Thread as-if-serial Semantics can be used to reorder the Semantics WithIn Thread as-if-serial Semantics. The Semantics WithIn Thread as-if-serial Semantics can be used to reorder the Semantics WithIn Thread as-if-serial Semantics. The Semantics WithIn Thread as-if-serial Semantics can be used to reorder the Semantics WithIn Thread as-if-serial Semantics.

Volatile is a lightweight synchronization mechanism provided by Java.

Volatile is lighter than synchronized (which is often referred to as heavyweight locking) because it does not cause thread context switching and scheduling.

Volatile features:

  • Visibility is guaranteed, atomicity is not guaranteed

  • Disallow command reordering

Instruction reordering means that during program execution, the compiler and CPU may reorder instructions for performance reasons.

2. Visibility and non-atomic verification

Volatile guarantees visibility, not atomicity

  • When a volatile variable is written, the JMM forces variables in that thread’s local memory to be flushed to main memory.
  • This write operation invalidates the cache of volatile variables in other threads.

Visibility verification

Suppose that thread A and thread B operate on the same variable in main memory. When thread A modifies the variable and writes the modified variable back to main memory, thread B does not know that the variable has been modified:

public class Demo01 {
    private static int num = 0;// Share variables
    public static void main(String[] args) {// main thread A

        new Thread(()->{  // subthread B
            while(num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;// Change the value of numSystem.out.println(num); }}Copy the code

Run to view the result:

Num =0, num=0, num=0, num=0

When adding volatile to num:

private volatile static int num = 0;
Copy the code

Run it again to see the result:

Subthread B gets the latest num value and exits the while loop.

When a variable is volatile, thread-local memory is invalid. When a thread changes a shared variable, it is immediately updated to main memory. When other threads read the shared variable, they read it directly from main memory.

Verify nonatomicity

Atomicity rejects multithreading operations. No matter multi-core or single-core, atomic quantities can only be operated by one thread at a time. In short, any operation that is not interrupted by the thread scheduler during its entire operation is considered atomic. For example, a=1 is atomic, but a++ and a+ =1 are not atomic.

A++ can be broken down into three operations:

  1. So let’s get the value of A
  2. And then a plus 1
  3. And then we write a back into memory
package com.cheng.volatiletest;

import java.util.concurrent.TimeUnit;

public class Demo02 {
    private static int num = 0;

    public static void add(a){
        num++;  // Non-atomic operation
    }


    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {// Create 20 threads
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {// Each thread executes add() 100 times
                    add();
                }
            }).start();
        }
        // If there are more than two active threads
        while (Thread.activeCount() > 2) {// main,GC
            Thread.yield();// Allocate CPU usage
        }
        System.out.println(Thread.currentThread().getName()+""+num); }}Copy the code

Run to view the result:

Because the add method is a non-atomic operation, the thread performing the add operation can be inserted by another thread, causing execution problems.

Using Synchronized or Lock ensures atomicity, so that when you execute add, you won’t be queued by another thread.

Using atomic classes

In addition to using Synchronized and Lock, atomic classes under the JUC package can also guarantee atomicity.

package com.cheng.volatiletest;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo02 {
    private static AtomicInteger num = new AtomicInteger();// Define AtomicInteger

    public static void add(a){
        num.getAndIncrement();// Atomically increments the current value by 1
    }


    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {// Create 20 threads
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {// Each thread executes add() 100 times
                    add();
                }
            }).start();
        }
        // If there are more than two active threads
        while (Thread.activeCount() > 2) {// main,GC
            Thread.yield();// Allocate CPU usage
        }
        System.out.println(Thread.currentThread().getName()+""+num); }}Copy the code

GetAndIncrement () the source code:

We get the old value, then pass it in, and call getAndAddInt () to do the atomic update. The actual core method is compareAndSwapInt(), which uses CAS to do the update.

3. Compare volatile with synchronized

● Volatile is a lightweight implementation of thread synchronization, so volatile is definitely better than synchronized; Volatile modifies only variables, while synchronized modifies methods, code blocks. With the release of new JDK versions, the execution efficiency of synchronized has also been greatly improved, and sychronized ratio is still very large in development.

● Multithreaded access to volatile variables does not block, whereas synchronized may.

● Volatile ensures visibility, but not atomicity; Synchronized guarantees atomicity as well as visibility.

● The keyword volatile addresses the visibility of variables across threads; The synchronized keyword addresses the synchronization of access to common resources between multiple threads.