1. Java concurrency knowledge

1.1 Memory visibility of shared variables

Java memory model, all the variables are stored in main memory, when a thread when using variable, the variable assignment in the main memory to their working memory (also called work space), the thread of variables all the operations (read, assignment, etc.) must be done in working memory, and cannot be directly read/write variables in main memory.

The invisible problem of memory is that the result of one thread operating on a shared variable is not visible to another thread because of its own working memory.

1.2 synchronized keyword

Synchronized block is an atomic built-in lock provided by Java. It is an exclusive lock and a reentrant lock. Thread execution automatically acquires an internal lock before entering a synchronized block, blocking access to the block by other threads. Because Java threads correspond one to one with native threads of the operating system, blocking causes switching from user mode to kernel mode to perform blocking, which is a time-consuming operation, while the use of synchronized causes context switching.

1.3 Volatile Keyword

Locking can solve the memory visibility problem of shared variables, but locks are cumbersome and can lead to overhead of thread context switching and thread rescheduling. So Java also provides a weak form of synchronization, using the volatile keyword, which guarantees that values written to the shared variable are immediately flushed back to main memory. It also retrieves the latest value from main memory. But the volatile keyword does not guarantee atomicity. Because the fetch-computation-write three-step operation is not atomic.

  • Volatile guarantees visibility only, not atomicity of operations
  • Volatile variables disallow instruction reordering optimization
example
private volatile int value;
public void inc(a) {
    // This statement is compiled into four statements, not to mention the specific machine code instructions
    ++value
}
/** * getField ensures that the value is correct * lCONST_1 * ladd when executing this and the previous instructions, the value may have been modified by another thread. So the data at the top of the stack is outdated and the putfield might synchronize the smaller values back to main memory
Copy the code

1.4 false sharing

What is pseudo-sharing

Because of the speed difference between the CPU and the main memory, one or more levels of Cache are added between the CPU and the main memory. Inside the Cache, the Cache is stored in rows. Each row is called a Cache row. A Cache row is the unit of data exchanged between the Cache and main memory.

When the CPU accesses a variable, it first checks the Cache line to see if there is a variable in it. If there is a variable in the Cache line, it directly fetches the variable. If there is a variable in the Cache line, it fetches the variable from main memory. Since the Cache row is a block of memory, it is possible to put multiple variables in a single Cache row. When multiple threads modify multiple variables in a Cache row at the same time, performance deteriorates compared to placing each variable in a single Cache row. This is called pseudo-sharing.

Why fake sharing

Since the unit of data exchanged between Cache and memory is the Cache line, the variables of the same program may be placed in the same Cache line according to the principle of locality (spatial and temporal locality) when the variables accessed by the CPU are not found in the Cache line.

Note: Pseudo-sharing occurs when multiple variables in the same cache row are accessed in multiple threads

How to avoid fake sharing

Prior to JDK 8, this was generally avoided by byte padding.

// Assume that a cache line is exactly 64 bytes in size
// Since the bytecode object header of the class object takes up 8 bytes, add these 7*8 bytes to make a total of 64 bytes.
public final class Test {
     public volatile long value = 0L;
     public long p1, p2, p3, p4, p5, p6;
}
Copy the code

JDK 8 provides sun.isc.Contended annotations, which are annotations in the RT package and only apply to Java core classes by default. If you want to use them in the user classpath, you need to add the JVM parameter -xx :-RestrictContended.

For example, in the LongAdder class, because there is a volatile array of cells, the elements of the atomic array often share the cache row because the memory address is contiguous. The Cell class uses this annotation: @sun.isc.Contended

@sun.misc.Contended static final class Cell{... }Copy the code

The resources

The Beauty of Concurrent Programming in Java (Book)