面试必问:读写一致性,你需要思考的问题
先說明下,本文要討論的多線程讀寫是指一個線程寫,一個或多個線程讀,不包括多線程同時寫的情況。
試想下這樣一個場景:一個線程往hashmap中寫數據,一個線程往hashmap中讀數據。 這樣會有問題嗎?如果有,那是什么問題?
相信大家都知道是有問題的,但至于到底是什么問題,可能就不是那么顯而易見了。
問題有兩點。
一是內存可見性的問題,hashmap存儲數據的table并沒有用voliate修飾,也就是說讀線程可能一直讀不到數據的最新值。
二是指令重排序的問題,get的時候可能得到的是一個中間狀態的數據,我們看下put方法的部分代碼。
可以看到,在put操作時,如果table數組的指定位置為null,會創建一個Node對象,并放到table數組上。但我們知道jvm中?tab[i] = new Node<>(hash, key, value, next);這樣的操作不是原子的,并且可能因為指令重排序,導致另一個線程調用get取tab[i]的時候,拿到的是一個還沒有調用完構造方法的對象,導致不可預料的問題發生。
上述的兩個問題可以說都是因為HashMap中的內部屬性沒有被voliate修飾導致的,如果HashMap中的對象全部由voliate修飾,則一個線程寫,一個線程讀的情況是不會有問題(這里是我的猜測,證實這個猜測正確性的一點依據是ConcurrentHashMap的get并沒有加鎖,也就是說在Map結構里讀寫其實是不沖突)。見下方區sora-zero同學的評論
?
創建對象的原子性問題
有的同學對于?Object obj = new Object();這樣的操作在多線程的情況下會拿到一個未初始化的對象這點可能有疑惑,這里也做個簡單的說明。以上java語句分為4個步驟:
以上步驟看起來也是沒有問題的,畢竟創建的對象要調用完構造方法后才會被引用。
但問題是jvm是會對指令進行重排序的,重排之后可能是第4步先于第3步執行,那這時候另外一個線程讀到的就是沒有還執行構造方法的對象,導致未知問題。jvm重排只保證重排前和重排后在單線程中的結果一致性。
注意java中引用的賦值操作一定是原子的,比如說a和b均是對象的情況下不管是32位還是64位jvm,a=b操作均是原子的。但如果a和b是long或者double原子型數據,那在32位jvm上a=b不一定是原子的(看jvm具體實現),有可能是分成了兩個32位操作。 但是對于voliate的long,double 變量來說,其賦值是原子的。
具體可以看這里https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7
?
數據庫中讀寫一致性
跳出hashmap,在數據庫中都是要用mvcc機制避免加讀寫鎖。也就是說如果不用mvcc,數據庫是要加讀寫鎖的,那為什么數據庫要加讀寫鎖呢?原因是寫操作不是原子的,如果不加讀寫鎖或mvcc,可能會讀到中間狀態的數據,以HBase為例,Hbase寫流程分為以下幾個步驟:
1.獲得行鎖
2.開啟mvcc
3.寫到內存buffer
4.寫到append log
5.釋放行鎖
6.flush log
7.mvcc結束(這時才對讀可見)
試想,如果沒有不走 2,7 也不加讀寫鎖,那在步驟3的時候,其他的線程就能讀到該數據。如果說3之后出現了問題,那該條數據其實是寫失敗的。也就是說其他線程曾經讀到過不存在的數據。
同理,在mysql中,如果不用mvcc也不用讀寫鎖,一個事務還沒commit,其中的數據就能被讀到,如果用讀寫鎖,一個事務會對中更改的數據加寫鎖,這時其他讀操作會阻塞,直到事務提交,對于性能有很大的影響,所以大多數情況下數據庫都采用MVCC機制實現非鎖定讀。
?
原文:Java架構筆記
免費Java高級資料需要自己領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并發分布式等教程,一共30G。??????????????????傳送門:??????????????????https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
轉載于:https://www.cnblogs.com/yuxiang1/p/11270285.html
總結
以上是生活随笔為你收集整理的面试必问:读写一致性,你需要思考的问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javascript 查找文本并高亮显
- 下一篇: vue防重复点击(指令实现)