multithreading problem

public class Demo {
    long counter = 0;
    class CountThread extends Thread{
        public void run(){
            for (int i = 0;i<10000;i++){
                counter++;
            }
        }
    }

    public static void main(String[] args) throws Exception{
        Demo demo = new Demo();
        for (int i = 0;i<10;i++)
            demo.new CountThread().start();
Thread.sleep(20000);
System.out.println(demo.counter);
}
                    
}
 
 

Visibility issues arise.

The output is <100000


In concurrent programming, we usually encounter the following three problems: atomicity problem, visibility problem, ordering problem. Let's take a look at these three concepts in detail:

1. Atomicity

Atomicity: That is, an operation or multiple operations are either all executed and the process of execution is not interrupted by any factor, or none of them are executed.

A very classic example is the bank account transfer problem:

For example, transferring 1,000 yuan from account A to account B must include two operations: subtracting 1,000 yuan from account A, and adding 1,000 yuan to account B.

Just imagine what the consequences would be if these two operations were not atomic. Suppose that after deducting 1,000 yuan from account A, the operation is suddenly terminated. Then, 500 yuan was withdrawn from B. After the 500 yuan was withdrawn, the operation of adding 1,000 yuan to account B was performed. This will cause account A to deduct 1,000 yuan, but account B does not receive the transferred 1,000 yuan.

Therefore, these two operations must be atomic to ensure that there are no unexpected problems.

What happens when the same is reflected in concurrent programming?

For the simplest example, think about what would happen if the assignment process to a 32-bit variable was not atomic?

i = 9;

If a thread executes this statement, I assume for the time being that assigning a 32-bit variable includes two processes: assigning a value to the lower 16 bits and assigning a value to the upper 16 bits.

Then there may be a situation: when the lower 16-bit value is written, it is suddenly interrupted, and at this time another thread reads the value of i, then the wrong data is read.

2. Visibility

Visibility means that when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can immediately see the modified value.

As a simple example, look at the following code:

//Code executed by thread 1
int i = 0;
i = 10;

//Code executed by thread 2
j = i;

If CPU1 is executing thread 1, and CPU2 is executing thread 2. It can be seen from the above analysis that when thread 1 executes the sentence i = 10, it will first load the initial value of i into the cache of CPU1, and then assign it to 10, then the value of i in the cache of CPU1 becomes 10 , but not immediately written to main memory.

At this point, when thread 2 executes j = i, it will first read the value of i from the main memory and load it into the cache of CPU2. Note that the value of i in the memory is still 0 at this time, so the value of j will be 0, and not 10.

This is the visibility problem. After thread 1 modifies the variable i, thread 2 does not immediately see the value modified by thread 1.

3. Orderliness

Ordered: The order in which the program is executed is executed in the order of the code. As a simple example, look at the following code:

int i = 0;              
boolean flag = false;
i = 1; //statement 1  
flag = true; //Statement 2

The above code defines an int type variable, defines a boolean type variable, and then assigns the two variables respectively. In terms of code order, statement 1 is before statement 2, so does the JVM guarantee that statement 1 will be executed before statement 2 when it actually executes this code? Not necessarily, why? Instruction Reorder may occur here.

下面解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。

但是要注意,虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?再看下面一个例子:

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

这段代码有4个语句,那么可能的一个执行顺序是:

那么可不可能是这个执行顺序呢: 语句2   语句1    语句4   语句3

不可能,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。

虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?下面看一个例子:

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2

//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序出错。

从上面可以看出,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

也就是说,要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。




Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324518948&siteId=291194637