volatile and synchronized in both good and bad


volatile


volatile keyword may have heard a lot of friends, and perhaps have used. Before Java 5, it is a controversial keywords used in the program because it often leads to unexpected results. After Java 5, volatile keyword was able to regain vitality.

**

Three concurrent concept

**
In concurrent programming, we often encounter the following three questions: atomicity issues, the visibility problem, ordering problem. We look at specific look at these three concepts:
1. Atomicity

Atomicity: i.e. one or more operations or all operations performed and the process will not be interrupted execution of any factor, or it is not performed.

A classic example is the bank account transfer problem:

For example transferred from the account to the account B A 1000 yuan, it necessarily includes two operations: 1000 yuan subtracted from account A, B to account plus 1,000.

Imagine if these two operations do not have the atomicity, what kind of consequences will result. After 1000 yuan if subtracts from the account A, the operation abruptly aborted. Then from B $ 500 removed, after removing 500, and then performs the account B to the operating element 1000 plus. Although this will result in account A minus 1,000 yuan, but did not receive the B accounts transferred from the 1,000 yuan.
2. Visibility

Visibility means that when multiple threads access the same variable, a thread changes the value of this variable, other threads can immediately see the modified values.

//线程1执行的代码
int i = 0;
i = 10;
 
//线程2执行的代码
j = i;

If 1 thread of execution is CPU1, executing thread 2 is CPU2. From the above analysis, the thread 1 executes when i = 10 the phrase, would first load the initial value of i to the CPU1 cache, then the assignment is 10, then the CPU1 cache among the value of i becomes 10 , but did not immediately written to main memory of them.

At this time, the thread 2 executes j = i, it will go to the main memory read and loaded into the value of i which CPU2 cache. Note that the value of i is 0 among the memory, so that it will j is 0, and not 10.

This is after the visibility problem, thread 1 to the variable i changed, did not see the value of the thread Thread 2 1 changes immediately.
3. orderliness

Ordering: sequential execution program that is executed in the order code. For a simple example, see the following code:

int i = 0;              
boolean flag = false;
i = 1;                //语句1  
flag = true;          //语句2

The above code defines a variable of type int, defines a boolean variable, respectively, and two variable assignment. The code sequence from the point of view, the statement is a statement in front of 2, then the JVM when you actually execute this code will ensure that a statement will be executed in the preceding sentence 2 do? Not necessarily, why? There may be reordered instruction (Instruction Reorder) occurs.

The following instructions explain what is reordering, in general, the processor in order to improve process efficiency, might enter the code optimization, it does not ensure the implementation of the program in individual statements in the order consistent with the order of the code, but it will the final implementation of the program is to ensure consistent results and the results of the implementation of the code sequence.

For example, the above code, the statement 1 and statement 2 who should perform the final result did not affect the program, it would be possible in the implementation process, the statement 2 statement is executed after the first execution 1.

Note, however, although the processor will reorder instructions, but it will ensure that the program will be the final result and order of the code to perform the same result, then rely on it to ensure it? A look at the following example:

int a = 10;    //语句1
int r = 2;    //语句2
a = a + 3;    //语句3
r = a*a;     //语句4

What would it be possible to order?
2-1-3-4?
Impossible, because the processor is performing reordering will consider data dependencies between instructions, if an instruction Instruction 2 Instruction 1 of the results must be used, then the processor will ensure Instruction 1 will be executed before Instruction 2.

  //线程1:
context = loadContext();   //语句1
inited = true;             //语句2
 
//线程2:
while(!inited ){
  sleep()
}

In the above code, since the statements statements 1 and 2 there is no data dependency, and therefore may be reordered. If the reordering occurs, execute the statement in 2 execution thread 1, thread 2 at a time would have thought initialization has been completed, it will jump out of the while loop, to perform doSomethingwithconfig (context) method, but this time there is no context and is initialized, it will cause an error.

As can be seen from the above, the instruction does not affect reordering of the execution of a single thread, but will affect the validity of concurrently executing threads.

In other words, in order to properly perform concurrent programs, we must ensure atomicity, visibility and orderliness. As long as there is not a guarantee, it may cause the program to run incorrectly.

x = 10;         //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4

1 is a direct statement to assign a value of 10 x, that is a thread of execution of this statement will be written directly to the value of 10 into the working memory.

2 statements actually contains two operations, it must first go read the value of x, then x is the value of the write working memory, although reading is the value of x and the value of x is written work memory of these two operations atomic operations, but not together the atomic operations.

The same, and x = x ++ comprises three operation x + 1: reading the value of x, are incremented, the new value is written.

Therefore, the above four operations only one statement includes a statement atomicity.

That is, only a simple reading assignment (digital and must be assigned to a variable, each assignment is not an atomic operation between variables) is an atomic operation.

One thing to note here, however: in the 32-bit platform, the 64-bit data needs to be read and the assignments are completed by two operations, which can not guarantee atomicity. But it seems in the latest JDK, JVM has also guarantee atomic operations on reading and assignment of the 64-bit data.

As can be seen from the above, Java memory model guarantees only the basic assignment and reading operations are atomic, atomicity to be achieved if a larger range of operation, can be achieved by synchronized and Lock. Since Lock to ensure synchronized and any time only one thread to execute the code block, then there is no natural atomicity problem, thus ensuring atomicity.

Visibility guarantee:
For visibility, Java provides the volatile keyword to ensure visibility.

When a shared variable is declared volatile, it will ensure that the modified values ​​will be updated immediately to the main memory, when there are other threads need to read, it will read the new value to memory.

The ordinary share variables can not guarantee the visibility, because after a normal shared variables are modified, and when is written to main memory is uncertain when the other threads to read at this time may still be the original value of the old memory, so We can not guarantee visibility.

In addition, synchronized and Lock can also guarantee visibility, synchronized and Lock ensures that only one thread acquires the lock and then synchronize code and to modify variables will be flushed to main memory of them before releasing the lock. So it can ensure visibility.
Ensure orderly:
in Java memory model, allowing the compiler and processor instructions to be reordered, but the reordering process will not affect the implementation of single-threaded programs, but it will affect the correctness of concurrent execution of multiple threads.

In Java, we can guarantee a certain "ordering" by the volatile keyword. Also can to ensure orderly and synchronized by Lock, obviously, synchronized and Lock to ensure that every time there is a thread synchronization code execution, the equivalent is to thread synchronization code execution order, naturally, to ensure the orderly.

In addition, Java memory model has some inherent "orderly", that is not required by any means, it is possible to ensure the orderly nature, this is often called happens-before principle. If the execution order of the two operations can not be deduced from the principle happens-before, then they can not guarantee their orderly, the virtual machine can freely reorder them.


Resolve volatile keyword


1.volatile two semantic keywords

Once a shared variable (member variables of the class, the class of static member variables) after being declared volatile, then have the two semantics:

1) to ensure visibility when different threads to this variable operation, that is a thread modifies the value of a variable, this new value to other thread is immediately visible.

2) prohibition command reordering.

    //线程1
boolean stop = false;
while(!stop){
    doSomething();
}
 
//线程2
stop = true;

Then the thread 1 at run time, the value of a variable copy will stop working on his memory of them.

So when Thread 2 change the value of the variable stop, but not enough time to write main memory among threads 2 turn to do other things, then thread 1 because they do not know to change the thread stop two pairs of variables, and therefore also been circulating down.

But after a volatile modification becomes different:

First: the use of volatile keyword will force the modified value is written immediately main memory;

Second: the use of the volatile keyword, then, when the thread 2 to modify, invalidate the working memory of the thread 1 in the cache variable stop cache line (reflected in the hardware layer, it is L1 or L2 cache of the CPU cache line corresponding to invalid);

Will go to the main memory to read due to an invalid thread 1 working memory cache variable stop cache line, so the value of the variable stop reading thread 1 again: third.

When the stop value is then modified in the thread 2 (here, of course, includes two operations, to modify the thread 2 in the working memory value, the modified value is then written to memory), the thread 1 causes the working memory in a cache line of cache variable stop invalid, then reading the threads 1, found to be invalid own cache line, it waits after a cache line corresponding main memory address is updated, then go to the corresponding main memory to read the latest value.

Then the thread 1 is to read the latest correct value.
2. How to ensure atomicity?

  public class Test {
    public volatile int inc = 0;
     
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
         
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

Some friends think is 10000. But in fact run it will find that each time you run the results are inconsistent, it is a number less than 10,000.

Some friends will be in doubt, do not, ah, the above is to be variable inc increment operator, due to the volatile to ensure the visibility, then the increase since the inc finished, in other threads can be seen in each thread modified value ah, so there are 10 threads were carried out 1000 operations, the final inc value should be 1000 * 10 = 10000.

There is there is a misunderstanding of, volatile keyword ensures visibility is not wrong, but wrong in the above procedure can not guarantee atomicity. Visibility can only guarantee that every reading is current value, but no way to guarantee the volatile atomic operations on variables.

In the already mentioned, it does not have self-energizing atomic operation, which comprises reading an original variable values ​​are incremented, memory write operation. Then that increment operator of three sub-operations may be divided executed, it may lead to the following situation occurs:

If a variable time inc is 10,

Thread 1 of variables increment operation, the thread 1 first reads the value of the original variable inc, then thread 1 is blocked;

Then thread 2 for variables increment operator, thread 2 also to read the original value of the variable inc, due to the variable inc thread 1 just read operation, but did not modify the operation of variables, so it will not cause the worker thread 2 void inc variable cache memory a cache line, the thread will go directly to the main memory 2 to read the value of inc, inc found value 10, and then are incremented, and the write working memory 11, and finally into the main memory .

Thread 1 then subsequently are incremented, since the value has been read inc, note that this case is in the working memory 11 in the thread 1 is the value of inc is still 10, so after one pair of thread are incremented inc inc and and then write working memory 11, and finally into the main memory.

Then two threads were carried out once since the increment operator, inc increased by only 1.

Explain here, may have a friend be in doubt, do not, ah, not a guarantee in front of a variable in a volatile variable is modified, it will cache line is invalid? Then another thread to read will read the new value of this right. This is a volatile variable rules above happens-before rules, but note that the variable thread 1 after a read operation, was blocked, it does not modify the value inc. Although volatile and can guarantee the value of the variable reading thread 2 inc is read from memory, but does not modify the thread 1, the thread 2 would not see the value changes.

Rooted here, increment operation is not atomic operations, but can not guarantee that any volatile operating variables are atomic.
3. ensure orderly ye?
In the aforementioned volatile keyword can disable command reordering, so volatile to ensure the orderly to a certain extent.

The volatile keyword prohibit instruction reordering has two meanings:

1) When the program executes a read operation or write operation volatile variables, change in front of the entire operation has been affirmative, and the result is already visible on the back of operation; operation in its rear certainly not performed;

2) During instruction optimization can not be performed in place behind the statement of access to volatile variables, nor can the volatile variables back into the front statement execution.


synchronized and volatile in both good and bad


synchronized keyword is to prevent multiple threads to execute a piece of code, it will very much influence the efficiency of program execution, and the volatile keyword in some cases better performance than synchronized, but be aware that the volatile keyword is no substitute for the synchronized keyword because the volatile keyword can not guarantee atomic operations. In general, the use of volatile must have the following two conditions:

1) a write operation is not dependent on the variable current value

2) The tag is not included in the invariant other variables having

In fact, these conditions indicate that the effective value independent of any state of the program can be written to these volatile variables, including the current status of variables.

In fact, my understanding is that the above two conditions need to ensure that operations are atomic operations, in order to ensure that the use of the volatile keyword program can execute properly when concurrency.

Published 24 original articles · won praise 6 · views 1015

Guess you like

Origin blog.csdn.net/weixin_44358757/article/details/105283991