Java多线程i++线程安全问题,volatile和AtomicInteger解释?
巴扎黑
巴扎黑 2017-04-18 09:53:42
0
5
841

在Java多线程中,i++和i--是非线程安全的。
例子:

public class PlusPlusTest {

    public static void main(String[] args) throws InterruptedException {
        Num num = new Num();
        ThreadA threadA = new ThreadA(num);
        ThreadB threadB = new ThreadB(num);
        threadA.start();
        threadB.start();
        Thread.sleep(200);
        System.out.println(num.count);
    }
}

class ThreadA extends Thread {
    private Num num;

    public ThreadA(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num.count++;
        }
    }
}

class ThreadB extends Thread {
    private Num num;

    public ThreadB(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num.count++;
        }
    }
}

class Num {
    int count = 0;

    public Num() {
    }
}

以上代码输出结果基本上不是2000,会比2000小。

原因:

在线程A中,i++的过程为:
temp1 = i; temp2 = temp1 + 1; i = temp2;
在线程B中,i++的过程为:
temp3 = i; temp4 = temp3 + 1; i = temp4;

在i=0的时候,线程A和B同时读取i=0。
线程A执行++后,i被修改成1。
线程B执行++后,i被修改,但还是1。

问:这样的解释对么?


想到把count变量申明为volatile,但是:

即使把count申明为volatile,输出的结果也不是2000,请问为什么?

class Num {
    volatile int count = 0;

    public Num() {
    }
}


最后

把count变量包装成AtomicInteger之后,输出的结果为2000,正确,这又是为什么?

巴扎黑
巴扎黑

reply all(5)
伊谢尔伦

Becausevolatile不保证操作的原子性,i++this operation is not an atomic operation.

大家讲道理

Q: Is this explanation correct?

In fact, it is not very appropriate. The operation of i++ should not be that troublesome. The reading value refers to reading the CPU.
An error occurred, the execution sequence is as follows:

  1. 线程1读到i的值为0,

  2. 线程2也读到i的值为0,

  3. 线程1执行了+1操作,将结果值1Write to memory,

  4. 线程2执行了+1操作,将结果值1Write to memory.

Even if count is declared as volatile, the output result is not 2000. Why?

The latest value of

volatile只能保证可见性,就是说实时读到i, but atomicity cannot be guaranteed, that is, the above execution sequence is completely allowed to occur. You can also refer to my answer to this question: https://segmentfault.com/q/10...

After wrapping the count variable into AtomicInteger, the output result is 2000, which is correct. Why is this?

AtomicInteger是原子的int,这个是由Java Implemented, roughly understand the source code:

 /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

The key point is compareAndSet(). This method will determine whether the values ​​​​of current and i meet the conditions: At this time, whether the value of i It is equal to current. If the condition is met, exit the loop directly. Otherwise, ++ it again until it is normal. compareAndSet(),这个方法会判断currenti的值是否满足条件:此时i的值是否和current相等,满足条件了直接退出循环,不然再++一遍,直到正常。
compareAndSet方法是由Java的unsafe实现的,这个应该很底层了,都是native方法,我也没研究过。不过,一般程序员是不会接触到unsafecompareAndSet method is implemented by Java's unsafe. This should be very low-level. They are all native methods. I have not studied them. However, the average programmer will not be exposed to unsafe programming.

Peter_Zhu

Volatile can only guarantee visibility, that is, you can read it immediately after someone else changes it, but others can also change it when you change it.
AtomicInteger is based on CAS (Compare And Swap).

CAS has 3 operands, the memory value V, the old expected value A, and the new value to be modified B. If and only if the expected value A and the memory value V are the same, modify the memory value V to B, otherwise do nothing. Two problems: (1) The CAS algorithm may still conflict. For example, between two threads A and B, A has entered the write memory but has not completed it. At this time, A reads the copy and the read is successful, and the two threads AB simultaneously Entering the write memory operation will inevitably cause conflicts. The essence of the CAS algorithm is not completely lock-free, but postpones the acquisition and release of locks until the CPU primitive is implemented, which is equivalent to narrowing the scope of the lock as much as possible; it directly realizes the change of the system state by mutual exclusion. The basic idea of ​​its use is copy-on-write - After modifying the copy of the object, use the CAS operation to replace the copy with the original. (2) ABA problem. If one of the threads modifies A->B->A, the other thread still reads A. Although the value is the expected value, it does not mean that the memory value has not changed.

巴扎黑

I am too lazy to write an answer. Here is a very good article: Concurrent Programming in Java: Volatile Keyword Analysis

小葫芦

volatile guarantees that the data obtained each time is the latest (read from memory), i++; --> i=i+1; If it is executed to i+1 without assigning a value to i, there is no guarantee that another thread will get it The data is the latest, and the latter one is an atomic operation, so it can be guaranteed that i = this will definitely be executed

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!