"Java concurrent programming art" of volatile

After learning of this chapter is memory model, for Volatile keywords have a more comprehensive understanding too, a point of knowledge analyzed and summarized.

The characteristics of volatile

volatile and synchronized in a single operation as

  • Visibility: write volatile field of assurance visible to all threads
  • Atomicity: single volatile read and write operations are atomic field (for example on a 32-bit machine, the read 64-bit long, double type), but not having atomic volatile ++

volatile的happens-before

This section uses set forth action happens-before relation of volatile

int a;
volatile boolean flag;
public void init(){
    a = 1; //①
    flag = true; // ②
}
public void doTask(){
    if(flag){ // ③
        result = a; // ④
    }
    ......
}

Suppose thread A executed init (), thread B executed doTask (); in this process, happens-before relation is divided into three categories:

  1. ① happens-before ②
  2. ③ happens-before ④
  3. ② happens-before ③

It happens-before diagram of this process is as follows:

Compliance procedures are the order of the rules, volatile variable rules and passing rules:

  • Program sequence rules: a thread, according to the order of the code book EDITORIAL preceding operation occurs at a later writing operation
  • volatile variable rules: Write operations on a variable advance occurred in the face of this variable after the read operation
  • Passing rules: if the operation occurs ahead A Procedure B, and B operate in the first operation occurs C, the A operation can be obtained in the preceding operation occurs C

volatile memory semantics

JMM compiler developed for the reordering of the rules of volatile

  • The second operation is a volatile write, no matter what is the first operation that can not be reordered. This rule ensures that the volatile write operation before will not be compiled discouraged sort to write after volatile.
  • When the first operation volatile read, no matter what the second is, can not be reordered. This rule ensures that the operation will not be volatile after reading compiled discouraged sorted before volatile read to.
  • When the first write operation is a volatile, volatile second operation is a read, no reordering.

volatile memory write semantics

When volatile write occurs, the local memory refresh main memory. Take the above example happens-before, the thread A executed when init () written into a volatile variable, B thread of execution doTask () reads a volatile variable. Change in memory state as shown in FIG.

After thread A write flag variable, the local memory updated shared variable (updated several refreshed several) to refresh main memory, then the same main memory and the local memory shared variables A thread.

volatile read memory semantics

When the thread to be read flag variable B, shared variables B includes local memory has been invalidated, the thread B reads the main memory had to shared variables. Thread B reads the local memory B will cause the value of the shared variable and main memory become consistent.

After two maps Taken together, reading a volatile variable reading thread B, thread A written before writing this volatile variable all visible shared variables will immediately become visible to the reader thread B.

Semantic summary

  • When (the changes made to their shared variables) write thread has written a volatile variable, in essence, it sent a written message to the next thread to read this variable thread
  • Read a thread reading corresponding volatile variable, in essence, received a written message sent by the thread (modify shared variables before volatile writing)
  • Then volatile variable thread writes write, read thread to volatile variables read, the process is the essence A thread sends a message to the main memory through the thread B

volatile memory implementation semantic

The volatile keyword implementation principle is mainly controlled by the memory barrier. When the compiler generates bytecode instructions in the sequence will be inserted in the specific memory barrier to inhibit reordering. For compiler, inserted discretion minimize the total number of barriers unlikely. To this end, JMM take a conservative strategy:

  • 在每个volatile写操作的前面加入StoreStore
  • 在每个volatile写操作的后面加入StoreLoad
  • 在每个volatile读操作的后面加入LoadLoad
  • 在每个volatile读操作的后面加入LoadStore

上面的插入策略十分保守,但它可以保证在任意处理器平台上(在X86里,写/写,读/读,读/写 是不会发生重排序的,而且只有StoreLoad一个内存屏障),任意的程序中都能实现正确的语义。

volatile写的内存语义实现

下面是保守策略下,volatile写插入内存屏障的指令序列示意图。

StoreStore保证在执行volatile写前,所有写操作的处理已经刷新至内存,保证对其他线程可见了。而StoreLoad的作用是避免后面还有其他的volatile读/写操作发生重排序。由于JMM无法准确判断StoreLoad所处的环境(比如结尾是return),所以有两种选择:

  1. 在volatile读前加上StoreLoad
  2. 在volatile写后加上StoreLoad

但是因为StoreLoad相比其他内存屏障更加消耗性能,考虑更多场景下是少写多读,所以将StoreLoad加在volatile写后。

讲到StoreLoad的性能问题,不得不提一下Unsafe里面的putOrderedObject()。 这个方法很有意思,乍一看命名是放一个有序的对象,但它是通过避免加上StoreLoad内存屏障来弥补volatile写的性能问题。这时可能会有朋友问,不加上volatile不会影响可见性吗?会影响可见性,但不会永远影响下去,最多就两三秒的延迟,就会将共享变量刷新至主内存。所以当延迟要求不高,性能要求高时,就可以采用这个方法(Unsafe不安全类,这个方法的实现在Atmoic***类里面)。

volatile读的内存语义实现

下面是保守策略下,volatile读插入内存屏障的指令序列示意图。

LoadLoad保证先执行volatile读再执行后续的读操作(禁止volatile读和后续的读发生重排序),而后的LoadStore保证先执行volatile读再执行写操作(禁止volatile读和后续的写发生重排序)。两者联合起来就是无论如何volatile读必须和程序顺序保持一致。

volatile执行时的优化

上面的volatile读/写的内存屏障插入策略都十分保守,但是在实际过程中,只要不改变volatile写/读的内存语义,编译器可以根据实际情况省略不必要的屏障。

int a;
volatile int v1 = 1;
volatile int v2 = 2;
void readAndWrite(){
    int i = v1;
    int j = v2;
    a = i + j;
    v1 = i + 1;
    v2 = j + 2;
}

针对readAndWrite()方法,编译器在生成字节码时会做如下优化。

按顺序下来,第一个volatile读先于第二个volatile,第二个volatile先于所有后续的写,故第一个volatile读一定不会被重排序;StoreStore保证普通写先于第一个volatile写,StoreStore又保证第一个volatile写先于第二个volatile写,最后安全起见插入StoreLoad。

上面的优化针对任意处理器平台,由于不同的处理器有不同“松紧度”的处理器内存模型,内存屏障的插入还可以根据具体的处理器内存模型继续优化。比如X86处理器,由于X86不会对读/读,读/写,写/写做重排序,所以面对X86处理器时,JMM会省略掉三种类型对应的内存屏障,保留StoreLoad内存屏障。

JSR-133为什么增强volatile的内存语义

在之前的版本,虽然不允许volatile变量间 的重排序,但是允许volatile和普通变量间的重排序。为了提供一种比锁更轻量级的线程间通信机制,专家组决定增强volatile的内存语义,严格限制volatile变量与普通变量的重排序,确保volatile的写-读和锁的释放-获取具有相同的内存语义。

由于volatile仅仅保证对单个volatile变量的读/写具有原子性,而锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。在功能上,锁比volatile更强大;在可伸缩性和性能上,volatile更有优势。具体看《Java理论与实践:正确使用volatile变量》

Guess you like

Origin www.cnblogs.com/codeleven/p/10963117.html