我們經常說的,xxx在多線程環(huán)境下會出問題。那么究竟是什么原因會導致這些問題呢?
原子性
所謂原子性,就是不可以再被分割。對于一個具有原子性操作來說,就是在執(zhí)行這個操作的過程中不會插入其他的操作
int i = 5; //原子性操作
i++; //非原子性操作
/**
* i++ 相當于 i = i + 1;
* 其中包含了三個操作:
* 1). 讀取 i 的值
* 2). 對 i 加 1
* 3). 重新賦值給 i
*/
非原子性操作可能會出現(xiàn)的問題:比如 i 的值為1,如果在 i++ 的第二個操作的時候,有其他的線程插入進來對變量 i 做了其他的操作,那么 i++ 之后,i 的值就不會是 2 了。
可見性
可見性,就是說某個線程對于一個共享變量做了修改,其他線程能夠立刻發(fā)現(xiàn)。
boolean flag = true;
while (flag) {
// do something...
}
/**
* 對于這段代碼,線程 A 在執(zhí)行while循環(huán)。此時,線程 B 將flag的值修改為false,線程 A 能夠馬上退出循環(huán)操作,就說明變量flag具有可見性。
*/
共享變量未滿足可見性可能會出現(xiàn)的問題
public class ThreadDemo { private static boolean flag = true; public static void main(String[] args) throws InterruptedException { //線程A //當 flag == false 跳出循環(huán),線程執(zhí)行結束 new Thread(() -> { while (flag) { //do something... } }).start(); //防止線程B先啟動 Thread.sleep(10); //線程B //在線程A啟動后,修改flag的值為false new Thread(() -> flag = false).start(); } }此時程序不會運行結束,因為變量flag沒有滿足可見性,線程B對變量flag的修改操作,對于線程A是不可見的。
有序性
通俗來講就是,代碼并不一定會按照順序來執(zhí)行,java虛擬機在執(zhí)行的過程中可能會改變順序來提高性能,但是不會改變程序整體的運行結果。
(1) int a = 2; (2) int b = 3; (3) int c = a + b; (4) int d = 6;就比如這四行代碼,(3)的執(zhí)行依賴于(1)和(2),所以,虛擬機在改變執(zhí)行順序的時候,不會把(1)或者(2)放到(3)后面去執(zhí)行,(4)跟其他三行沒有依賴關系。最終執(zhí)行的順序是,(1)(2)一定在(3)之前執(zhí)行,(4)可以在任意位置執(zhí)行。這么看來似乎有序性顯得不是太重要,可是在多線程的環(huán)境下就會發(fā)生問題。
//有兩個線程 A B boolean isDone = false; //線程A new Thread(() -> { save(object); //假設做保存操作 isDone = true; }).start(); //線程B new Thread(() -> { while (true) { if (isDone) { Object obj = getObject(); //使用obj對象做后續(xù)的工作 } } }).start();上面是一段偽代碼,想表述的意思是,線程A做保存操作,然后設置標記變量isDone為true;線程B根據標記變量判斷線程A是否保存完畢,當isDone == true,就認為線程A已經保存完畢,然后取出線程A保存的對象,做后續(xù)的工作。
如果此時,線程A中發(fā)生了重排序的情況,線程A里面的兩行代碼交換了執(zhí)行的順序。而此時,設置了isDone的值,還沒有執(zhí)行save操作的時候,線程B開始執(zhí)行了,就會發(fā)生問題,線程B的get操作不會取到任何對象。
對于以上的三個問題,Java提供了關鍵字synchronized來解決。當然還有volatile以及jdk并發(fā)包里面的各種工具,后面的文章我會介紹相關的知識