1. 原子性
操做是原子的,則它不可被分割。從另外一個線程的視角來看,它不該該看到這個操做的中間狀態,只能看到兩種狀態:①還沒開始執行 ② 已經執行結束。
在多個線程訪問臨界資源時,若是臨界區的代碼不是原子的,則一個線程執行到某個中間狀態,而後被時鐘中斷,另外一個線程接着執行,就會覆蓋數據或訪問到不正確的數據。
2. 可見性
因爲在存儲器層次結構中,更高層的數據是更低層數據的一個子集,該數據副本可能存在存儲系統的不一樣層中。爲了提升性能,高層數據寫回底層並非實時的,所以可能形成在某時刻高層數據和底層數據的不一致。
在多核cpu下,因爲每一個核內部都內置了cache,各個核的cache可能緩存了同一個內存地址的數據,cache之間若是沒有某種機制協調,會致使數據不一致(各個線程看到相同數據的值不同)。
當一個線程修改了共享數據後,咱們但願其餘線程也能看到這個改變後的結果。若是其餘線程看不到這個修改後的數據,繼續訪問緩存的舊數據,就可能出現問題。
3. 有序性
① 哪些地方可能產生重排序?
源代碼-->字節碼/機器碼-->存儲系統-->CPU
會形成重排序的有:① 編譯器 ② 存儲系統 ③ CPU的執行單元。
② 爲何要重排序呢?
爲了提升性能。好比更利於cpu的流水、亂序執行;cpu的異步寫入store buffer;store buffer的合併寫操做等。
③ 重排序會形成什麼影響呢?
好比:
a = 1;
b = 2;
在執行b = 2這行代碼時,咱們可能想固然的以爲a = 1應該已經執行完了,即咱們認爲在這個時刻,應該可以看到a的值是1。遺憾的是,因爲重排序的存在,這一點是不能保證的。java
固然,重排序也不是隨便就打亂順序的(保證As-if-serial語義),單線程下存在依賴關係的代碼順序是不能被打亂的,而在多線程下因爲多個線程數據的依賴關係比較複雜,難以預測和分析,所以交給軟件開發者來指明,而硬件開發者則須要提供相關指令(如內存屏障)供軟件開發者使用。
總結:
只有原子性是不能保證線程安全的。就算操做原子了,其運算的結果不能讓另外一個線程看到,另外一個線程仍是讀的舊值,那結果仍是錯誤的。
可是就算有了原子性和可見性,也不能保證線程安全。咱們原本指望在某行代碼處能看到前面代碼的運算結果,結果因爲重排序,前面代碼被重排到後面了。所以在此處咱們看到了舊值(可見是可見了,但指望的代碼排到後面了還未執行)。
所以要保證線程安全,原子性、可見性和有序性缺一不可。