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

在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,正确,这又是为什么?

巴扎黑
巴扎黑

membalas semua(5)
伊谢尔伦

Oleh kerana volatile tidak menjamin keatoman operasi, i++ operasi ini bukan operasi atom.

大家讲道理

S: Adakah penjelasan ini betul?

sebenarnya tidak begitu sesuai Operasi i++ tidaklah begitu menyusahkan Nilai bacaan merujuk kepada membaca CPU .
Ralat telah berlaku, urutan pelaksanaan adalah seperti berikut:

  1. 线程1 membaca bahawa nilai i ialah 0,

  2. 线程2 juga membaca bahawa nilai i ialah 0,

  3. 线程1 melakukan operasi +1 dan menulis nilai hasil 1 ke dalam ingatan,

  4. 线程2 melaksanakan operasi +1 dan menulis nilai hasil 1 ke dalam ingatan.

Walaupun kiraan diisytiharkan sebagai tidak menentu, hasil keluaran bukanlah 2000. Mengapa?

volatile hanya boleh menjamin keterlihatan, yang bermaksud bahawa nilai terkini i boleh dibaca dalam masa nyata, tetapi ia tidak dapat menjamin atomicity, iaitu, urutan pelaksanaan di atas dibenarkan sepenuhnya berlaku. Anda juga boleh merujuk kepada jawapan saya untuk soalan ini: https://segmentfault.com/q/10...

Selepas membungkus pembolehubah kiraan ke dalam AtomicInteger, hasil keluaran ialah 2000, yang betul Mengapa ini?

AtomicInteger ialah atom int Ini dilaksanakan oleh Java Mari kita fahami secara kasar kod sumber:

 /**
     * 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);
    }

Intinya ialah compareAndSet() Kaedah ini akan menentukan sama ada nilai current dan i memenuhi syarat: 此时i的值是否和current相等 Jika syarat dipenuhi, keluar terus daripada gelung , 再++ akan diulang sehingga biasa.
compareAndSet方法 dilaksanakan oleh Java的unsafe Ini sepatutnya merupakan kaedah yang sangat rendah, dan saya belum mempelajarinya. Walau bagaimanapun, pengaturcara biasa tidak akan didedahkan kepada pengaturcaraan native. unsafe

Peter_Zhu

Meruap hanya boleh menjamin keterlihatan, iaitu, anda boleh membacanya serta-merta selepas orang lain mengubahnya, tetapi orang lain juga boleh mengubahnya apabila anda menukarnya.
AtomicInteger adalah berdasarkan CAS (Banding Dan Tukar).

CAS mempunyai 3 operan, nilai memori V, nilai jangkaan lama A dan nilai baharu yang akan diubah suai B. Jika dan hanya jika nilai jangkaan A dan nilai memori V adalah sama, ubah suai nilai memori V kepada B, jika tidak, jangan lakukan apa-apa. Dua masalah: (1) Algoritma CAS mungkin masih bercanggah Contohnya, antara dua utas A dan B, A telah memasuki memori tulis tetapi belum menyelesaikannya Pada masa ini, A membaca salinan dan bacaan berjaya kedua-dua benang AB serentak Memasuki operasi memori tulis pasti akan menyebabkan konflik. Intipati algoritma CAS tidak sepenuhnya bebas kunci, tetapi menangguhkan pemerolehan dan pelepasan kunci sehingga primitif CPU dilaksanakan, yang bersamaan dengan mengecilkan skop kunci sebanyak mungkin ia secara langsung menyedari perubahan keadaan sistem dengan pengecualian bersama Idea asas penggunaannya ialah salin-tulis - Selepas mengubah suai salinan objek, gunakan operasi CAS untuk menggantikan salinan dengan yang asal. (2) Masalah ABA Jika salah satu utas mengubah suai A->B->A, utas lain masih membaca A. Walaupun nilainya adalah nilai yang dijangkakan, ini tidak bermakna nilai memori tidak berubah.

巴扎黑

Saya terlalu malas untuk menulis jawapan Berikut ialah artikel yang sangat bagus: Pengaturcaraan Serentak dalam Java: Analisis Kata Kunci Meruap

小葫芦

Jaminan yang tidak menentu bahawa data yang diperolehi setiap kali adalah yang terkini (dibaca daripada ingatan), i++ --> i=i+1; Data yang diperolehi oleh benang adalah yang terkini, dan yang terakhir adalah operasi atom, jadi ia boleh dijamin bahawa i = ini pasti akan dilaksanakan

Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan