Updated on 11 August 2020

On the date of publication, some students commented that Volatile shared variables are not atomic. From a procedural point of view, Volatile variables are not atomic, but visible.

I also emphasize atomicity for reading and writing individual volatile variables from a memory semantic perspective. A lock is used to synchronize reads and writes to a volatile variable with those to a normal variable, and the execution is the same.

The most typical example is that 64-bit long and double variables can be split into 32-bit double reads and writes, which may not perform as expected if they are concurrently executed. Although the JSR-133 memory model has been enhanced since JDK5, only 64-bit longs and doubles can be written in two 32-bit writes, and the reads can only be in place once, atomically. Volatile variables read and write atomically.

So this i++ is a compound operation, which is the starting point of what other students said was not atomic.

preface

For Android developers, the belief that knowledge of concurrent programming is very weak has been one of the weaknesses of personal advancement. For developers with little experience in concurrent practice, technical books and blogs can be shy and difficult to understand. From the beginning of this article, try to break the basic knowledge points of concurrent programming one by one.

Because of ignorance and inertia, let us feel the ceiling of technology!

The interview 10 q

This article combines the personal actual interview experience and the recent study summarizes and summarizes, welcome everybody big guy praise support.

Through the interview 10 questions, let everyone master singleton double check pattern and static inner class singleton pattern, and understand the principle. From the principle and then lead to the focus of this paper: volatile and synchronized.

Q 1: Are there any design patterns that are commonly used in Android development?

The answer: The singleton pattern, the constructor pattern, and the factory pattern are commonly used. Especially in the singleton pattern double check pattern and static class singleton pattern; The ability to ensure that multithreaded objects are unique and do not create multiple instances that cause program execution errors or affect performance.

Interpretation: Although there are many design patterns, personally, singleton patterns are often used. Although the interview before the surprise review, but a nervous interview, what egg use. So be sure to answer what you know and guide the interviewer to what you know.

Question 2: Write double-checked and static class singleton code on paper?

Psychological activities: fortunately, I have written many times before the interview, the problem is not big, just write out:

Double-check mode:

public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }}Copy the code

Static inner class pattern:

public class Singleton { private Singleton(){ } public static Singleton getSingleton(){ return Inner.instance; } private static class Inner { private static final Singleton instance = new Singleton(); }}Copy the code

Write it up and pass it to the interviewer: Both double-checked and singleton modes are thread-safe, and both provide delayed initialization, reducing unnecessary performance overhead.

Q 3: What should I pay attention to in double-checked mode?

public class Singleton { private volatile static Singleton singleton; Private Singleton() {} //1 public static Singleton getSingleton() {//2 if (Singleton == null) {// 3.1 synchronized (Singleton. Class) {//3.2 if (Singleton == null) {//3.3 Singleton = new Singleton(); //4 } } } return singleton; }}Copy the code

A: The following points need to be noted in double-checked mode:

  1. Constructors must be private, preventing other objects from creating instances directly.
  2. Provide a static method to obtain a unique instance;
  3. Double-checked mode, however, means that there are two levels of checking during instance creation. The first layer is the outermost null statement:If (singleton == null), the judgment is not locked to avoid the first checksingletonObjects are notnull, multi-thread locking and initialization operations; Passes if the current object is not createdsynchronizedKeyword synchronization code block, hold currentSingleton.classLock to ensure thread safety, and then a second check.
  4. SingletonClass held bysingletonInstance reference needsvolatileKeyword modifier, because in the last stepsingleton = new Singleton(); Reordering may occur while creating the instance, resulting insingletonObject escapes, causing another thread to acquire an uninitialized object.

Question 4: Why reorder?volatileHow do keywords prohibit reordering?

A: Reordering is a means by which editors and processors reorder sequences of instructions to optimize program performance. As long as followas -if-serialSemantics (no matter how reordered, the execution result of a single-threaded program will not change). Therefore, the compiler may, to optimize performance, reorder steps 2 and 3 in the figure below. This reorder is allowed because it does not change the execution result of a program in a single thread (currently only the thread monopolizes the code block).This is not a problem in a single-threaded environment, but in a multi-threaded environment, the results of the program will be corrupted. As shown in the figure below, when thread B nulls in the first step, the reference of the Singleton instance is already non-null, so it does not enter the lock application stage and directly accesses the object. However, the object has not been initialized, so there will be various problems in the actual use of the object. volatileThe modified variable itself has visibility and atomicity. Visibility refers to the read value of a volatile variable that is the most recently modified value in all threads. And atomicity means rightA singleVariable reads and writes are atomic. These features are created because they are added before the assembly instruction for the shared variableLockInstruction, theLockThe prefix instruction does two things on a multicore processor:

1. Write the data of the current processor cache line back to the system memory;

2. Writing back to memory invalidates data cached by other processors.

Ps: Single-core processor can only execute one thread at a time. Multi-threading refers to the context switching and scheduling of different threads by single-core CPU. Multi-core processors can have multiple threads (one thread per core) running concurrently at the same time, so synchronization is very important. Modern cpus are basically multi-core.

Since the volatie variable is visible, the writing-read relationship is established happens-before. From the perspective of memory semantics, when thread A writes A volatile variable, thread A sends A notification to A thread that will read the volatiel variable next. In principle, when writing a volatile variable, the JAVA Memory Model (JMM) flusher shared variables from the thread’s local memory to main memory. When a volatile variable is read, the thread’s local memory is invalidated and the variable is read from main memory. Threads communicate implicitly with each other by sharing the program’s volatile variables (shared state) and by sharing state with write and read operations.

The JMM implements this volatile memory semantics by limiting partial reordering by the compiler and processor.

Make the following three rules for compiler optimization:

  • The first operation is a read on a volatile variable. Whatever the second operation is, reordering is prohibited.
  • The first operation is a write to volatile. The second operation is a read to volatile, which forbids reordering.
  • The second operation, a write to a volatile variable, disallows reordering regardless of the first operation.

It is understood from rule 2 that reordering can be prohibited by adding the volatile keyword to modify a reference to a singleton.

According to these three rules, the compiler inserts an appropriate, conservative memory barrier (a set of CPU instructions that limits the order of memory operations) into the instruction sequence when the bytecode is generated.

  • Insert StoreStore barriers before volatile writes;
  • Insert the StoreLoad barrier after volatile writes;
  • Insert LoadLoad barrier after volatile read;
  • Insert LoadStore barrier after volatile read;

The above memory barriers are very conservative, and the compiler will also partially optimize the generation of bytecode to reduce some unnecessary memory barriers to improve performance. Different processors continue to optimize based on their memory model.

Ps :JMM is to shield the inconsistency of the underlying hardware memory model and provide a set of standard memory model for the top-level development, so that the development focus on business development.

Question 5: What is the happens-before rule?

A: Starting with JDK5, the new JSR-133 memory model is used, which defines the happens-before rule:

  1. The procedural order rule: for each action in a thread, happens-before any subsequent action in that thread;
  2. The monitor principle: the unlocking of a lock is happens-before the subsequent locking of that lock;
  3. Volatile rule: A write to a volatile variable, happens-before any subsequent read to the volatile field;
  4. Transitivity: If A happes-before B, B happens-before C, then A happens-before C;
  5. Thread A performs threadb.start (), and start() happens before all operations in ThreadB;
  6. Jion () rule: if thread A executes threadb.jion () and returns successfully, all operations of ThreadB are happens-before thread A returns successfully from jion().

Question 6: Point 2 of the rule talks about locking. What does locking do in double-checked singletons?

A: In code 3.2, the synchronized keyword is used to synchronize the singlewind. Class object, ensuring that only one thread instantiates the Singletion Class object in a multi-threaded environment. In Java, every object can be used as a lock:

  1. For normal synchronous methods, the lock is the current instance object;
  2. For statically synchronized methods, the lock is the Class object of the current Class;
  3. For synchronized method blocks, the lock is a Synchoized bracketed Class object.

Q 7: Does the static inner class singleton use locks?

A: Yes, the JVM performs Class initialization during the Class initialization phase (after the Class is loaded, but before the thread uses it). The JVM acquires a lock that can synchronize multiple threads’ initialization of the same Class.

When thread A obtains the initialization lock, other threads can only wait to acquire the initialization lock. Thread A performs class static initializations and initializations of static fields. A double-checked reorder does not affect the result because no other thread can catch the initialization lock. When thread A completes initialization, it releases the lock and notifies the thread waiting to acquire the initialization lock. According to the monitor rule in the happens-befroe relationship, when another thread acquires the initial lock, all the initialization operations of thread A are already visible, the static object has been initialized, and other threads do not need to initialize again.

Question 8: understand the principle of lock, lock storage where do you know?

A: The JVM(Java Virtual Machine) implements method synchronization and code block synchronization based on entering and exiting Monitor objects. A synchronized code block is inserted at the beginning of the synchronized code block after compilation using monitorenter, and at the end or exception of the synchronized code block using Monitorexit. The Monitorenter must be paired with the monitorexit directive. Any object has a Monitor associated with it, and when a Monitor is held, it is locked. When the thread executes the Monitorenter instruction, it attempts to acquire ownership of the object’s monitor, that is, the lock on the object. Method prefixes the instruction of the method with the ACC_SYNCHRONIZED modifier.

Synchronized uses locks that are stored in Java object headers; If the object is an array, store the object header with a width of 3 characters, where 1 character is used to store the array length. Non-array, 2 word wide store object headers. On a 32-bit vm, 1 word width =4 bytes =32 bits.

Question 9: Since you know about Java object headers, you should know about several states of locking upgrade, say something about it?

A: In Java SE6, in order to reduce the performance cost of acquiring and releasing locks, theBiased lockingandLightweight lock. This means that there are four lock states from low to high: no lock state, biased lock state, lightweight lock state, and heavyweight lock state. The status of a lock is defined in terms of threads competing for it. In 32-bit JVM running state, Mark Work storage structure: Biased locking:Threads do not have race conditions in most cases, and using synchronization costs performance, while biased locking is an optimization of locking that eliminates synchronization and improves performance. When a thread acquires a lock, it sets the lock flag bit of the object header to 01 and goes into bias mode. Biased locks allow one thread to hold the lock until it is contested by other threads. == only one thread enters the critical section.

Lightweight lock: When thread A obtains the biased lock, thread B enters the contention state and needs to acquire the lock held by thread A. Then thread A cancels the biased lock and enters the lock free state. Thread A and thread B alternately enter the critical section, and the biased lock cannot be satisfied, swelling to the lightweight lock, and the lock flag bit is set to 00. == “Multiple threads enter the critical section alternately.

Heavyweight locks: Lightweight locks hold when multiple threads alternately enter critical sections. However, if multiple threads enter the critical region at the same time, the hold cannot be held, and the expansion to the heavyweight lock == “multiple threads enter the critical region at the same time.

10. Q: Why is Synchronized sufficient?

Volatile is lighter in synchronization than Synchronized and can effectively reduce CPU thread context switching and scheduling. At the same time, Volatile atomic operations are writes and reads to individual Volatile variables, not comparable to Sychronized operations for entire methods or blocks of code.

conclusion

Basically, every question will involve some knowledge points, and the interviewer will ask questions from different directions, leading to different knowledge points. For example, the following questions lead to Java’s memory model, which are frequently asked in interviews.

In this article, you need to master two ways to write the singleton pattern: Java: Singleton pattern I only recommend two. You also need to know about volatile and synchronized.

Due to the limited personal ability, there are mistakes welcome correction; Or feel the direction is not good, welcome guidance, thank you very much.

Last hope: Like & follow =true

My lot

Most of the information in this article comes from the Art of Concurrent Programming in Java.