[Kill Thread Part.2-2] volatile, atomicity, JMM application

Posted by lpxxfaintxx on Fri, 28 Jan 2022 20:55:20 +0100

[Kill Thread Part.2-2] volatile, atomicity, JMM application

1, volatile keyword

1. What is volatile

Volatile is a synchronization mechanism, which is lighter than synchronized or Lock related classes, because using volatile does not cause expensive behaviors such as context switching.

If an traversal is modified to volatile, the JVM knows that this variable may be modified concurrently.

However, the overhead is small and the corresponding capacity is small. Although volatile is used for synchronization to ensure thread safety, volatile cannot do the atomic protection of synchronized. Volatile can only play a role in very limited scenarios.

2. Inapplicability of volatile: it depends on the previous state

①a++

public class NoVolatile implements Runnable{
    volatile int a;
    AtomicInteger realA = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;
            realA.incrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NoVolatile r = new NoVolatile();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(r.a);
        System.out.println(r.realA.get());
    }
}

② Depends on previous value

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Description: situations where volatile is not applicable 2
 */
public class NoVolatile2 implements Runnable {

    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable r =  new NoVolatile2();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(((NoVolatile2) r).done);
        System.out.println(((NoVolatile2) r).realA.get());
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            flipDone();
            realA.incrementAndGet();
        }
    }

    private void flipDone() {
        done = !done;
    }
}

The result may be true or false.

3. Application of volatile

①boolean flag

boolean flag, if a shared variable is only assigned by each thread from beginning to end without other operations, volatile can be used to replace synchronized or atomic variables, because the assignment itself is atomic, and volatile ensures visibility, so it is enough to ensure thread safety.

/**
 * Description: situations where volatile applies 1
 */
public class UseVolatile1 implements Runnable {

    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable r =  new UseVolatile1();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(((UseVolatile1) r).done);
        System.out.println(((UseVolatile1) r).realA.get());
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            setDone();
            realA.incrementAndGet();
        }
    }

    private void setDone() {
        done = true;
    }
}

The key is whether the copy operation is related to the previous value

② Trigger of variable before refresh

4. Two functions of volatile

① Visibility

Before reading a volatile variable, you need to invalidate the corresponding local cache. In this way, you must read the latest value from the main memory. Writing a volatile attribute will immediately flush into the main memory.

② Prohibit instruction reordering optimization

Solve the disorder of single case double lock

5. Relationship between volatile and synchronized

Volatile can be regarded as a lightweight version of synchronized: if a shared variable is only assigned by each thread from beginning to end without other operations, volatile can be used to replace synchronized or atomic variables, because the assignment itself is atomic and volatile ensures visibility, so it is enough to ensure thread safety.

2, Visibility extension

1. Measures to ensure visibility

Besides volatile, synchronized, Lock, concurrent collection, therad Join() and thread Start () and so on can ensure visibility.

2. Correct understanding of synchronized visibility

synchronized ensures not only atomicity, but also visibility.

Using the above method, you can ensure that the code on the synchronized code block is also visible.

3, Atomicity

1. What is atomicity

A series of operations can be executed successfully or not. There will be no half execution. They are inseparable.

Why is i + + not atomic

But you can use synchronized to achieve atomicity

2. What are the atomic operations in Java

Assignment operations of basic types (int, byte, boolean, short, char, float) except long and double

All assignment operations referring to reference, whether 32-bit machines or 64 bit machines

java.concurrent.Atomic.* Atomic operations of all classes in the package

3. Atomicity of long and double

Official documents:

Writing 64 bit values can be divided into two 32-bit operations. Read errors can be solved by volatile

Conclusion: on a 32-bit JVM, long and double operations are not atomic, but they are atomic on a 64 bit JVM.

4. Atomic operation + atomic operation= Atomic operation

Simply combining atomic operations together does not guarantee that the whole is still atomic.

Fully synchronized HashMap is not completely safe

4, Interview FAQ 1: single case mode

JMM application example: 8 writing methods of singleton mode and the relationship between singleton and concurrency

① Role of singleton mode

  • Save memory and Computing
  • Ensure that the results are correct
  • Convenient management

② Applicable scenario

  • Stateless tool class: for example, day tool class
  • Global information class: for example, we record the number of visits to the website on a class

Eight ways of writing singleton mode

1. Hungry Han formula (static constant) [available]

/**
 * Description: hungry Han formula (static constant) is available
 */
public class Singleton1 {
    //When the class is loaded, it will be instantiated
    private final static Singleton1 instance = new Singleton1();
    private Singleton1() {

    }

    private static Singleton1 getInstance() {
        return instance;
    }
}

2. Hungry Chinese style (static code block) [available]

/**
* Description: hungry Chinese style (static code block)
*/
public class Singleton2 {
   private final static Singleton2 instance;

   static {
       instance = new Singleton2();
   }

   private Singleton2() {
       
   }
   
   public static Singleton2 getInstance() {
       return instance;
   }
}

3. Lazy (thread unsafe) [not available]

/**
 * Description: lazy (thread unsafe)
 */
public class Singleton3 {
    private static Singleton3 instance;

    private Singleton3() {

    }
    public static Singleton3 getInstance() {
        //Two threads execute this line at the same time. It is found that it is empty. Two instances are created, which does not conform to the single instance.
        if (instance == null) {
            instance = new Singleton3();
        }
        return instance;
    }
}

4. Lazy (thread safe) [not recommended]

/**
 * Description: lazy (thread safe) (not recommended)
 */
public class Singleton4 {
    private static Singleton4 instance;

    private Singleton4() {

    }
    public synchronized static Singleton4 getInstance() {
        if (instance == null) {
            instance = new Singleton4();
        }
        return instance;
    }
}

The disadvantage is that the efficiency is too low

5. Lazy (thread unsafe, synchronous) [not recommended]

/**
* Description: lazy (thread unsafe) (not recommended)
*/
public class Singleton5 {
   private static Singleton5 instance;

   public Singleton5() {

   }

   public static Singleton5 getInstance() {
       if (instance == null) {
           //If both threads enter the if, although synchonized will make one thread wait, one thread will release the lock after creating the instance, and the other thread will still create the instance
           synchronized (Singleton5.class) {
               instance = new Singleton5();
           }
       }
       return instance;
   }
}

6. Double check! [recommended!]

/**
* Description: double check (recommended for interview)
*/
public class Singleton6 {
   private volatile static Singleton6 instance;

   private Singleton6() {

   }

   public static Singleton6 getInstance() {
       if (instance == null) {
           synchronized (Singleton6.class) {//Class lock
               if (instance == null) {
                   instance = new Singleton6();
               }
           }
       }
       return instance;
   }
}

Advantages: thread safety; Delayed loading; High efficiency;

Why double check? Is single check OK? Can you put synchronized on the method? (poor performance)

Why use volatile?

  • The new object is not an atomic operation
  • There are actually three steps to creating a new object

① ② ③ steps may be reordered, resulting in concurrency problems. Suppose that thread 1 creates an object and assigns a reference. It has not called the construction method to assign a value to the attribute. Then thread 2 judges that rs is not empty. If it returns the object instance directly, it will find that the attribute in the object is empty, and a problem occurs. A null pointer (NPE) problem occurs. Using volatile prevents instruction reordering.

7. Static internal class [recommended]

/**
 * Description: static internal class mode, available
 */
public class Singleton7 {
    private Singleton7() {

    }

    //Slacker
    private static class SingletonInstance {
        private static final Singleton7 instance = new Singleton7();
    }

    public static Singleton7 getInstance() {
        return SingletonInstance.instance;
    }
}

8. Enumeration [recommended]

/**
 * Description; Enumeration singleton
 */
public enum Singleton8 {
    INSTANCE;

    public void whatever() {

    }
}

Comparison of different writing methods

  • Hungry man: simple, but not lazy loading
    • If the creation of an object requires a configuration file, it does not apply
  • Sluggard: thread safety issues
    • If there are too many resources to load at the beginning, use lazy loading
  • Static inner class: available
  • Double check: for interview
  • Enumeration: the actual development is the best
    • The enumeration class is decompiled and found to be a static variable, and then loaded lazily
    • Simple writing
    • Thread safety is guaranteed
    • Avoid deserialization breaking singletons

Interview FAQs

  • The disadvantage of starving Han style
    • Waste of pre loading resources
  • Lazy shortcomings
    • Complex writing
    • Multithreading may not be safe
  • Why use double check? Isn't it safe not to use it?
  • Why use volatile in double check mode?
    • Three steps to create objects to prevent reordering and visibility problems.
  • How to choose and which single instance implementation scheme is the best?
    • Enumeration class
      • Simple implementation
      • Lazy style
      • Thread safety

5, Interview FAQ 2

1. What is the Java Memory Model

Cause - > differences with JVM memory model and Java object model - > what is JAVA memory model

  • Is the norm
  • Reordering, visibility, atomicity
    • JMM's abstraction of main memory and thread memory
    • volatile keyword and synchronized relationship
      • Similarities and differences between volatile and synchronized
      • synchronize expansion, source code, ensure visibility, atomicity, close friends (you can make nearby codes visible)
    • Which operations are atomic

2. What is atomic operation? What atomic operations are there in Java? Is the process of generating objects atomic?

3. What is memory visibility?

cpu cache diagram at all levels.

4. Are 64 bit double and long atomic when written

In practice, we don't need to add volatile. The commercial JVM has already helped us to implement it