Atomicity of concurrent strange

I'm on a hit with everyone in concurrent bully visibility , this section we continue to crusade against one of three atomic evil.

Sequence set forth, the atomic

One or more characteristics of operation is not interrupted during the atomic referred CPU execution.

I understand that an operation can not be divided, that is atomicity . In concurrent programming environment, meaning that as long as the atomicity of the thread started this series of operations, either all executed or not executed all, the presence of half of the execution is not allowed.

We tried to compare concurrent programming and database transactions from two aspects:

1, in the database

Atoms concept is like this: the transaction is treated as an integral whole, which is included in the operation performed either all, or all not be executed. And the transaction if an error occurs during execution, will be rolled back to the state before the start of the transaction, as the transaction does not perform the same. (In other words: the transaction is either executed or a no is executed)

2, in concurrent programming

Atoms concept is like this:

  • The first to understand: a thread or process in the implementation process, there is no context switch occurs.
    • Context switching: refers to (switch premise is to get the right to use the CPU) CPU switch from one process / thread to another process / thread.
  • The second understanding: we have a thread or a plurality of operation (indivisible), characteristics of the CPU in the process of execution is not interrupted, known as atomic. (During execution, once an interrupt occurs, it will context switch occurs)

As can be seen from the foregoing description of atoms, the atom concept between two concurrent programming and database is somewhat similar: are stress, an atomic operation can not be interrupted! !

Rather than the atomic operation is represented with a picture like this:

A thread executing a while (not yet completed execution), to sell B CPU thread to execute. Such an operation has many operating system, a very short time-consuming the sacrificial thread switching, to improve the utilization of the CPU, thereby improving overall system performance; this operation the operating system is called "time slice" switch.

First, the cause of atomic issues arise

Through the concept of atomic described in the preface, we conclude that: the cause of atomic issues of shared variables appear between threads is context switching.

So then, we have to reproduce the atomic problem with an example.

First, the definition of a bank account entity classes:

    @Data
    @AllArgsConstructor
    public static class BankAccount {
        private long balance;

        public long deposit(long amount){
            balance = balance + amount;
            return balance;
        }
    }复制代码

Then open multiple threads to the shared bank accounts deposit operations, each one yuan deposit:

import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;

/**
 * @author :mmzsblog
 * @description:并发中的原子性问题
 * @date :2020/2/25 14:05
 */
public class AtomicDemo {

    public static final int THREAD_COUNT = 100;
    static BankAccount depositAccount = new BankAccount(0);

    public static void main(String[] args) throws Exception {

        ArrayList<Thread> threads = new ArrayList<>();
        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread thread = new DepositThread();
            thread.start();
            threads.add(thread);
        }

        for (Thread thread : threads) {
            thread.join();
        }

        System.out.println("Now the balance is " + depositAccount.getBalance() + "元");
    }

    static class DepositThread extends Thread {
        @Override
        public void run() {
            for (int j = 0; j < 1000; j++) {
                depositAccount.deposit(1);   // 每次存款1元
            }
        }
    }
}复制代码

Run the above procedure several times, each time the results are almost not the same, we occasionally get the desired result 100*1*1000=100000元, the following is the result of several runs I have cited:

The reason is because the above case

balance = balance + amount;复制代码

This code is not atomic operations, the balance of which is a shared variable. It may be interrupted in a multithreaded environment. So atomicity problem appeared naked. as the picture shows:

Of course, if the balance is a local variable, it will not appear even in the case of multi-threading problems (but this does not apply to share a bank account local variables ah, otherwise it is not shared variable, ha ha, equivalent to nonsense), because local variables It is the current thread private. As is shown in the for loop in the variable j.

Welcome to public concern number "Java learning of the Road" for more dry!

But then, even the shared variable, Xiao Bian I will not allow such a problem, so we need to address it, then atomicity problem is more profound understanding of concurrent programming.

Second, to solve the problem of atomicity context switch caused by

2.1, use local variables

The scope of local variables is the internal method, that is to say when the method executed, the local variables was abandoned, local variables and methods with the total death. The call stack is the stack frame and methods with the total death, so the call stack into local variables in there is quite reasonable. In fact, local variables is really put in the call stack.

It is because each thread has its own call stack, local variables stored in the call stack inside each thread, not shared, so naturally there is no concurrency issues. Sum up: no sharing, you can not go wrong.

But here if the local variable, then 100 threads each deposit of 1,000 yuan, the last is from 0 deposit, not cumulative, will lose the results had wanted to show. Therefore the method is not feasible.

As used herein, a single thread can guarantee atomicity, like, because they do not fit the current scene, and therefore can not solve the problem.

2.2, comes to ensure atomicity

In Java, the read operation and assignment of the basic variable data types are atomic operations.

For example, the following lines of code:

// 原子性
a = true;  

// 原子性
a = 5;     

// 非原子性,分两步完成:
//          第1步读取b的值
//          第2步将b赋值给a
a = b;     

// 非原子性,分三步完成:
//          第1步读取b的值
//          第2步将b值加2
//          第3步将结果赋值给a
a = b + 2; 

// 非原子性,分三步完成:
//          第1步读取a的值
//          第2步将a值加1
//          第3步将结果赋值给a
a ++;      复制代码

2.3、synchronized

All the java code into something atomicity it is certainly not possible, something a computer can process time is always limited. So when can not achieve atomicity, we have to use a strategy to make this process appears to be in line with atomic. So there was synchronized.

may be synchronized to ensure the visibility of the operation, to ensure atomicity operations may be the result.

Within an object instance, synchronized aMethod(){}the synchronized method multiple threads access the object can be prevented.

If an object has multiple synchronized method, as long as one thread to access a synchronized method which, other threads can not access the object in any synchronized method at the same time.

So, the way we only need to deposit into a synchronized set here will be able to guarantee the atomicity.

 private volatile long balance;

 public synchronized long deposit(long amount){
     balance = balance + amount; //1
     return balance;
 }复制代码

Plus synchronized later, when a thread is not executed before the completion of the addition of synchronized deposit this method, other threads can not be synchronized implementation of this modified code. Therefore, even when the implementation of lines of code 1 is interrupted, other threads can not access the variable balance; so from a macro point of view, then, the end result is to ensure the correctness. But the middle of the operation is interrupted, we do not know. For more information, you can look CAS operation.

PS: For the above variables balance we might be a little puzzled: Why variable balance plus the volatile keyword? In fact, the purpose of adding the volatile keyword here is balance in order to ensure the visibility of variables, ensure access to synchronized code block every time to read the latest value from main memory.

Therefore, here

 private volatile long balance;复制代码

It can also be replaced synchronized modification

 private synchronized long balance;复制代码

And because it can guarantee the visibility of our first article in a strange visibility of concurrency it has been introduced before.

2.4, Lock lock

public long deposit(long amount) {
    readWriteLock.writeLock().lock();
    try {
        balance = balance + amount;
        return balance;
    } finally {
        readWriteLock.writeLock().unlock();
    }
}复制代码

Lock lock guarantee a similar atomic theory and synchronized, not repeat them here.

Some readers may wonder, Lock locks here are the release operation of the lock, but does not seem synchronized. In fact, Java compiler will automatically add modifications before and after the synchronized method or block of code lock lock () and unlock unlock (), the benefits of doing so is to lock lock () and unlock unlock () must appear in pairs after all, forget to unlock the unlock (), but a deadly Bug (meaning other threads can only wait the dead).

2.5 atomic operation type

If the class definition to use atomic property to guarantee the correctness of the results, it is necessary to modify the entity classes as follows:

    @Data
    @AllArgsConstructor
    public static class BankAccount {
        private AtomicLong balance;

        public long deposit(long amount) {
            return balance.addAndGet(amount);
        }
    }复制代码

JDK classes provide many atomic operations to ensure atomicity operation. The most common example of basic types:

AtomicBoolean
AtomicLong
AtomicDouble
AtomicInteger复制代码

These classes underlying atomic operations using CAS mechanism, this mechanism ensures that the entire assignment atoms can not be interrupted, so as to ensure the correctness of the final result.

Synchronized and compared, the operation type corresponds to ensure atomic atomic microscopically, and is synchronized to ensure atomicity from the macro.

2.5 The above solution, each small operations are atomic, atoms such as those classes AtomicLong modify operation, crud their own operation is atomic.

So, just small operations are in line with each atom of which is not representative of the entire constitution is in line with atomicity of it?

Obviously not.

It will still produce thread-safety issues, such as the whole process is a process 读取A-读取B-修改A-修改B-写入A-写入B; so, if the modification A is completed, the operating loss of atomicity, then thread B B began to perform a read operation, then there will be problems atomicity .

In short I do not think the use of thread-safe classes, all of your code is thread-safe! You should always review your code from the overall atomic. Like on the following example:

    @NotThreadSafe
    public class UnsafeFactorizer implements Servlet {

        private final AtomicReference<BigInteger> lastNum = new AtomicReference<BigInteger>();
        private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();

        @Override
        public void service(ServletRequest request, ServletResponse response) {
            BigInteger tmp = extractFromRequest(request);
            if (tmp.equals(lastNum.get())) {
                System.out.println(lastFactors.get());
            } else {
                BigInteger[] factors = factor(tmp);
                lastNum.set(tmp);
                lastFactors.set(factors);
                System.out.println(factors);
            }
        }
    }复制代码

Although it all classes with the atoms to operate, but not between the respective atomic operation. In other words: A thread in the implementation of such statements in the else lastNumber.set(tmp)after, and perhaps other threads execute the if statement in the lastFactorys.get()method, then thread A before continuing execution lastFactors.set(factors)method update factors!

From this logic process, thread-safety problem has occurred.

It destroys the methods of 读取A-读取B-修改A-修改B-写入A-写入Bthis whole process, after the completion of write A to perform reading the other thread B, B values has led to read the value of B is not written. So atomic appeared.

Well, that is my content and the method of atomic little understanding and summarized by these two articles we will generally mastered the visibility, atomicity problems common in concurrent and their common solution.

At last

Some examples of paste atom problems often seen.

Q : often heard it said that the long type variable is complicated by the presence of risk and subtraction operations on 32-bit machines, in the end is not it so?

Answer : the presence of concurrent risk of addition and subtraction is right for the long variable operating on 32-bit machines.

The reason is : the thread switch atomicity problems caused.

Of non-volatile types long and double variables is 8-byte 64-bit, 32-bit machines have to people clicking operation into two 32-bit read or write the variable, a thread may read a 32-bit value is high, the lower 32 bits have been changed by another thread. So the official recommended best to longdouble variable is declared as volatile or synchronized lock synchronize to avoid concurrency issues.

Reference article:

  • 1, Java concurrent programming geeks time combat
  • 2、https://juejin.im/post/5d52abd1e51d4561e6237124
  • 3 https: //www.cnblogs.com/54chensongxia/p/12073428.html
  • 4、https://www.dazhuanlan.com/2019/10/04/5d972ff1e314a/


No welcome public concern: the Java learning path

Personal blog site: www.mmzsblog.cn


Guess you like

Origin juejin.im/post/5e5db07cf265da574727969a