有沒有字節工牌,Java併發安全的根本原因都得懂

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}}],"text":"真正的大師永遠懷着一顆學徒的心","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"引言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"併發問題一直是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"領域的高階問題,要想掌握它不僅需要了解","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"JVM","attrs":{}}],"attrs":{}},{"type":"text","text":"的內存模型,更需要對計算機底層硬件有深入的理解。本文主要探討下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"併發安全問題的根源所在,通過對根源問題對探究,加深對於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"併發安全的理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"併發安全問題分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道程序猿編寫的代碼都是跑在具體的硬件架構上面的,只是目前的高級語言系統屏蔽了很多底層硬件細節。但是如果想要對於併發問題有深入的理解,還是需要對底層計算機硬件系統的細節有更多的瞭解。因此要想分析併發安全問題的根本原因,我們需要從問題現象出發,刨根問底,深入研究才能找到問題的答案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"計算機內存模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先從計算機硬件系統出發,我們可以將計算機系統簡化爲三大部件,即","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"、內存以及","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"IO","attrs":{}}],"attrs":{}},{"type":"text","text":"設備。隨着技術的不斷髮展,計算機硬件也有了長足的發展。各大部件的能力與日俱增。但是有一個問題一直圍繞在計算機硬件結構周圍,那就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"、內存以及","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"IO","attrs":{}}],"attrs":{}},{"type":"text","text":"設備之間的數據訪問速度有着巨大的速度差異。正式由於這種訪問速度的巨大差異造成了影響程序性能的最大因素正是最慢的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"IO","attrs":{}}],"attrs":{}},{"type":"text","text":"設備,因此如果需要提升整體的性能,僅僅提高某一項是不夠的,要從整體出發,充分發揮","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"性能優勢。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/91/91bd7d2fe7c121f60d063b00d76da3fb.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了應對這種數據讀取速率差異,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":" 中增加了高速緩存,來平衡其與內存的速度差異。操作系統通過增加進程、線程,以便與最大可能分時複用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":",充分挖掘","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在CPU中都會有一個高速緩衝區,在實際運行過程中,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"首先從計算機主存中將數據複製到高速緩衝區中。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"在進行運算時,直接基於高速緩衝區的數據進行運算,邏輯運算之後,再將高速緩衝區的數據刷新到主存中。通過這樣的方式,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"的執行指令的速度就可以大大提升。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在單核CPU時代,程序中所有的線程都跑在這顆獨苗","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"中,由於所有當線程都是操作同一塊","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"的緩存,因此據一個線程對緩存的操作,對另外一個線程來說一定是可見的。因此不存在線程安全的問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c86bdeb6354c38ed82fd2ebc66126cb9.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是當在多核","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"場景下,線程跑在不同當","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"中,因此對變量進行邏輯操作時,對其他線程不可見,因此會存在併發安全問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/73/73b94d49214106e53ce84b0bcad2e446.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到此,我們分析出了併發安全的第一個根源,即緩存導致的數據可見性問題,由於存在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"高速緩存,不同線程所在不同的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"在計算後結果互不可見,這才導致了併發安全問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"JVM內存模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"JVM","attrs":{}}],"attrs":{}},{"type":"text","text":"定義的內存模型實際是計算機硬件架構在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"JVM","attrs":{}}],"attrs":{}},{"type":"text","text":"中的映射體現。內存模型屏蔽了不同操作系統與內存硬件的訪問差異。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"的內存模型如圖所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f2/f2f3d7dfb5f33a449545c8420b437669.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"JVM","attrs":{}}],"attrs":{}},{"type":"text","text":"啓動運行之後,操作系統會爲該","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"JVM","attrs":{}}],"attrs":{}},{"type":"text","text":"進程分配製定的的內存空間,這部分內存空間即爲上圖中的主內存。實際我們的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"程序的所有工作都由線程來完成,而每個線程都會有一小塊內存,即所謂的工作內存。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"中的線程在執行的過程中,會先將數據從主內存中複製到線程的工作內存,然後再執行計算,執行計算之後,再把計算結果刷新到主內存中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6e/6e9f8f429ce5b45028d68b223664bb30.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們一起來分析下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"count++","attrs":{}}],"attrs":{}},{"type":"text","text":"在多線程場景下無法得到預期結果的原因。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d2/d2891a3b0887f1219a0c5a6d2a276c3c.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"count++","attrs":{}}],"attrs":{}},{"type":"text","text":"的操作 看上去是執行了一條指令實際上包含了三條指令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)首先,需要把變量","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"count","attrs":{}}],"attrs":{}},{"type":"text","text":"從內存加載到工作線程的工作內存中;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)加載後在工作內存中執行","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"+1","attrs":{}}],"attrs":{}},{"type":"text","text":"操作;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(3)最後,將計算結果寫入內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上文所說的,由於計算機的大部件之間存在數據處理速度差異,處理一項任務時往往是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"在等待其他部件完成後才進行後續的操作,爲了提高","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"的工作效率,可以在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"等待期間讓出CPU使用權,讓","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":"去處理其他事情,這就是所謂的分時複用。那麼在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"多線程場景下,必定也會發生線程切換,如下圖所示,由於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"count++","attrs":{}}],"attrs":{}},{"type":"text","text":"不具備運算的原子性,導致了線程在運行過程中發生線程切換,最終導致輸出結果與預期不一致。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/05/051811ddf962e92831b56a97ee49f517.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們把一個或者多個操作在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CPU","attrs":{}}],"attrs":{}},{"type":"text","text":" 執行的過程中不被中斷的特性稱爲原子性。如上面的例子,如果保證了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"count++","attrs":{}}],"attrs":{}},{"type":"text","text":"的原子特性,那麼就不會有併發安全問題了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文從計算機內存模型出發,再到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"JVM","attrs":{}}],"attrs":{}},{"type":"text","text":"內存,分析了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Java","attrs":{}}],"attrs":{}},{"type":"text","text":"併發安全問題根本原因分別是多線程下的數據可可見性以及線程切換帶來的原子性問題。那麼這些問題應該怎麼解決呢?在下一篇文章中,我們再繼續探討。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我是慕楓,感謝各位小夥伴點贊、收藏和評論,文章持續跟新,我們下期再見!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微信搜索:慕楓技術筆記,優質文章持續更新,我們有學習打卡的羣可以拉你進,一起努力衝擊大廠,另外有很多學習以及面試的材料提供給大家。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7f/7ff6f4cf7dc92ed189149727ace4dc43.gif","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章