什麼是原子性,說白了就是整個程序中的所有操作要麼都執行,要麼都不執行。
但是 volatile 可以保證可見性,但是不能保證原子性,所以是一個輕量級的同步機制。
例如:下面代碼加了volatile,但是不能保證原子性,number的最終結果不是20000.
class Data {
public volatile int number;
public void add(){
number++;
}
}
public class Main {
public static void main(String[] args) {
Data data = new Data();
for (int i=0; i<10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<2000; i++) {
data.add();
}
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() >=2) {
Thread.yield();
}
System.out.println(data.number);
}
}
爲什麼volatile不能保證原子性?
首先,number++這個操作不能保證原子性,numberi從底層來看被分爲三部分:將主物理內存中number的值拷貝到線程的棧內存中 ---》 線程在自身的棧內存中進行number+1操作 ---》 線程將新的number的值寫回主物理內存。
然後,在執行的時候,假設線程A、B、C在某一時刻都哪去到了number = 0的值,然後都在自身的棧內存中進行了 number+1的操作,然後線程B先寫回主物理內存,此時,線程 A、C被掛起,然後線程B寫結束之後,通知其他線程該number的值被修改了,但是!其他線程(A、C)被喚醒的速度太快,沒來的及接受通知就已經將A得到number=1再一次寫入主物理內存,造成了丟失寫值的操作。所以最後 data.number一般都會小於20000。
怎麼解決?
1、使用synchronized關鍵字,但是有個問題就是synchronized太“重”了,所以不是最優的解決方案。
2、使用 java.util.concurrent.atomic 這個包,此案例中使用 AtomicInteger 就可以保證原子性。
修改如下:
class Data {
public AtomicInteger atonumber = new AtomicInteger();
public void atoAdd() {
atonumber.getAndIncrement();
}
}
public class Main {
public static void main(String[] args) {
Data data = new Data();
for (int i=0; i<10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<2000; i++) {
data.atoAdd();
}
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() >=2) {
Thread.yield();
}
System.out.println(data.atonumber);
}
}