※volatile
尤於 CPU 的速度比主存還快,所以每個 Thread 都會有一個專屬的 cache,將主存的資料從主存拷過去
volatile 能保證 1.內存可見性和 2.排序性
.volatile 只能宣告在全域變數
.內存可見性:volatile 有揮發性的意思,意思就是用完就丟,在 java 裡的意思是值有改變就去主存抓
以上面的圖來說,要是值改變了,就相當於沒有 CPU cache 了,所以會到主存去抓 (保下面的賣票例子不可用 volatile,因為沒有原子性)
.排序性:CPU 會將我們寫的程式碼重排,如寫在第一行,但不一定就是先執行,不過他保證結果是一樣的,但可惜只是單線程一樣,多線程有可能會不一樣,而加上這個關鍵字可以禁止 CPU 重排
.只能解決一寫多讀的情形
.++i 是原子性;i++ 不是原子性,可用 AtomicXXX 或 LongAdder 產生原子性,高併發時用 LongAdder 較快,否則 AtomicXXX 較快
※
※此例如果不加 volatile 有可能兩個 CPU 同時抓到,所以迴圈裡的 flag 永遠都是 false了
※此例也可用 synchronized 或 Lock,但效能比較差
※不代表 volatile 可取代 synchronized
volatile 並沒有互斥性,也不能保證原子性
互斥性:像 synchronized 和 Lock 就有,一次只能一個進入,但指的是相同的物件
例:HttpSession 可以鎖定成功;但 HttpServletRequest 就不可能鎖定成功
※還沒加原子性:雖然在減 1 時,其他線程有看見,但有可能還沒寫,其他線程就讀到舊的,因為 volatile 沒有原子性
※加原子性的 AtomicInteger 後,使用兩個線程看起來好像可以,實際上還是有問題,因為此例是偶數票,每次少兩張,最後是 0,但用更多的線程去跑或改成單數票又不行了
.因為雖然加了原子性,但在改之前,其他線程還是讀的到 (資料庫的不可重覆讀)
如第 1 次,四個線程都讀到 10,然後都減 1,但有原子性,所以最後是 6
而第 2 次,四個線程都讀到 6,然後都減 1,還是有原子性,所以最後是 2
而第 3 次,四個線程都讀到 2,然後都減 1,還是有原子性,所以最後是 -2
也就是說一次只能有一個線程讀取,所以必需要有互斥性
※
public class App {
private volatile boolean flag = false;
public void xxx() {
new Thread(() -> {
try {
Thread.sleep(200);
flag = true;
System.out.println("oooooooooooooo");
} catch (InterruptedException e) {
}
}).start();
new Thread(() -> {
while (true) {
if (flag) {
System.out.println("xxxxxxxxxxxxxx");
break;
}
}
}).start();
}
public static void main(String... ss) {
new App.xxx();
}
}
※此例如果不加 volatile 有可能兩個 CPU 同時抓到,所以迴圈裡的 flag 永遠都是 false了
※此例也可用 synchronized 或 Lock,但效能比較差
※不代表 volatile 可取代 synchronized
volatile 並沒有互斥性,也不能保證原子性
互斥性:像 synchronized 和 Lock 就有,一次只能一個進入,但指的是相同的物件
例:HttpSession 可以鎖定成功;但 HttpServletRequest 就不可能鎖定成功
※賣票(必需要有互斥性)
public class AppTest {
volatile AtomicInteger ticket = new AtomicInteger(10);
// Integer ticket = 10;
public static void main(String... s) {
new AppTest().xxx();
}
public void xxx() {
Runnable run = () -> {
for (;;) {
// if (ticket > 0) {
System.out.println("票" + ticket.get());
if (ticket.get() > 0) {
try {
TimeUnit.SECONDS.sleep(1);
// System.out.println(--ticket);
System.out.println(ticket.decrementAndGet());
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
};
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
}
※還沒加原子性:雖然在減 1 時,其他線程有看見,但有可能還沒寫,其他線程就讀到舊的,因為 volatile 沒有原子性
※加原子性的 AtomicInteger 後,使用兩個線程看起來好像可以,實際上還是有問題,因為此例是偶數票,每次少兩張,最後是 0,但用更多的線程去跑或改成單數票又不行了
.因為雖然加了原子性,但在改之前,其他線程還是讀的到 (資料庫的不可重覆讀)
如第 1 次,四個線程都讀到 10,然後都減 1,但有原子性,所以最後是 6
而第 2 次,四個線程都讀到 6,然後都減 1,還是有原子性,所以最後是 2
而第 3 次,四個線程都讀到 2,然後都減 1,還是有原子性,所以最後是 -2
也就是說一次只能有一個線程讀取,所以必需要有互斥性
※如果沒有 if,只讓它一直加或一直減,最終會是正確的
例:i++ 在底層運作是
int temp = i;
i = i+1;
return temp;
這三步是不能切割的,但 volatile 無法保證這一點
※
※increment 有可能會有同時進入 CPU 的情形,使用 volatile 也沒有用
※java 1.5 新增了 java.util.concurrent.atomic 套件,專門做原子的操作
※incrementAndGet方法就是前置遞增了,而遞減單字是 decrement
※原子性採用 CAS (compare and swap) 算法,屬於樂觀鎖
※CAS 有三個值v和a都是主存抓來的,b是替換值,只有 v 和 a 的值相等才會將 b 的值取代 v 的值(CPU2 的圖錯了,v=1, a=0 才對)
※以上圖為例,CPU1 從主存抓到 i 的值是 0,此時 CPU2也進來抓到0
這時又換 CPU1,比較過後是一樣的,所以更新為 1,i 更新成功為1
這時又換 CPU2,因為 i 是 volatile,所以 v=1, a=0, 什麼也不做
※如果是 CAS 自旋,那 CPU2 還會繼續,這是 v 和 a 都是 1,就看 b 是什麼就可以取代
※判斷和塞新值要看成一個做法,也就是說不會在判斷時,另一個 Thread 進來的情況
※此例當然也是可以用 synchronized,但 synchronized 效能比較差
※還可看高手寫的文章
※原子性
表示不能切割,如後置遞增/減例:i++ 在底層運作是
int temp = i;
i = i+1;
return temp;
這三步是不能切割的,但 volatile 無法保證這一點
※
public class MyThread implements Runnable {
private int increment;
// private AtomicInteger increment = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
System.out.println(increment++);
// System.out.println(increment.getAndIncrement());
} catch (InterruptedException e) {}
}
public static void main(String... ss) {
MyThread my = new MyThread();
for (int i = 0; i < 10; i++) {
new Thread(my).start();
}
}
}
※increment 有可能會有同時進入 CPU 的情形,使用 volatile 也沒有用
※java 1.5 新增了 java.util.concurrent.atomic 套件,專門做原子的操作
※incrementAndGet方法就是前置遞增了,而遞減單字是 decrement
※原子性採用 CAS (compare and swap) 算法,屬於樂觀鎖
※CAS 有三個值v和a都是主存抓來的,b是替換值,只有 v 和 a 的值相等才會將 b 的值取代 v 的值(CPU2 的圖錯了,v=1, a=0 才對)
※以上圖為例,CPU1 從主存抓到 i 的值是 0,此時 CPU2也進來抓到0
這時又換 CPU1,比較過後是一樣的,所以更新為 1,i 更新成功為1
這時又換 CPU2,因為 i 是 volatile,所以 v=1, a=0, 什麼也不做
※如果是 CAS 自旋,那 CPU2 還會繼續,這是 v 和 a 都是 1,就看 b 是什麼就可以取代
※判斷和塞新值要看成一個做法,也就是說不會在判斷時,另一個 Thread 進來的情況
※此例當然也是可以用 synchronized,但 synchronized 效能比較差
※還可看高手寫的文章






