java并发之初识
一:并發(fā)編程的難點
1:原子性問題
- 操作系統(tǒng)做任務切換,可以發(fā)生在任何一條CPU指令執(zhí)行完成后;
- CPU能保證的原子操作是指令級別的,而不是高級語言的操作符;
n++不是原子操作的,而是3條指令
2:可見性問題
- 可見性是指一個線程對一個變量進行修改,另外一個線程可以看的到
- 可見性問題是由CPU的緩存導致的,多核CPU均有各自的緩存,這些緩存要與內存進行同步。(其實就是多線程環(huán)境下,一個線程對一個變量的改變了,而另一個線程沒看到,那么的話還是按照原來的變量的值進行計算的話,那么就會出錯)。
3:有序性問題
- 在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序;
- 重排序不會影響單線程的執(zhí)行結果,但是在并發(fā)情況下,可能會出現(xiàn)詭異的BUG。
二:并發(fā)編程
1:并發(fā)編程的目標
解決多核多線程下,造成的 緩存不一致問題,指令重排問題,處理器優(yōu)化問題
- 在cpu和主存之間添加緩存,在多線程下會存在緩存一致性問題(可見性問題)
- 處理器內部為了使運算單元盡可能的被充分利用,處理器可能會對輸入的代碼進行亂序處理。這就是處理器優(yōu)化。(原子性問題)
- 很多編程語言的編譯器也會有類似的優(yōu)化,比如Java虛擬機的即時編譯器(JIT)也會做指令重排。(指令重排問題)
2:并發(fā)編程的內存模型
- 為了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統(tǒng)中多線程程序讀寫操作行為的規(guī)范。
- 通過這些規(guī)則來規(guī)范對內存的讀寫操作,從而保證指令執(zhí)行的正確性。它與處理器有關、與緩存有關、與并發(fā)有關、與編譯器也有關。他解決了CPU多級緩存、處理器優(yōu)化、指令重排等導致的內存訪問問題,保證了并發(fā)場景下的一致性、原子性和有序性。
3:這個內存模型是什么
- JMM是Java Memory Model的縮寫,Java線程之間的通信由JMM控制,即JMM決定一個線程對共享變量的寫入何時對另一個線程可見。
- JMM定義了線程和主內存之間的抽象關系,通過控制主內存與每個本地內存(抽象概念)之間的交互,JMM為Java程序員提供了內存可見性的保證。
- JMM是一種規(guī)范,目的是解決由于多線程通過共享內存進行通信時,存在的本地內存數(shù)據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執(zhí)行等帶來的問題。
4:源代碼和指令間的重排序
為了提高性能,編譯器和處理器常常會對指令做重排序。重排序有3種類型,其中后2種都是處理器重排序。這些重排序可能會導致多線程程序出現(xiàn)內存可見性問題。
- 1.編譯器優(yōu)化重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。
- 2.指令級并行重排序:現(xiàn)代處理器采用了指令級并行技術來將多條指令重疊執(zhí)行,如果不存在數(shù)據依賴性,處理器可以改變語句對應機器指令的執(zhí)行順序。
- 3.內存系統(tǒng)的重排序:由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。
5:解決CPU帶來的重排序
- CPU內存屏障:
- 1.LoadLoad:禁止讀和讀的重排序;
- 2.StoreStore:禁止寫和寫的重排序,
- 3.LoadStore:禁止讀和寫的重排序,
- 4.StoreLoad:禁止寫和讀的重排序。
- Java內存屏障:
6:解決編譯器帶來的重排序
(1):如何解決
JMM使用nappens-before規(guī)則來闡述操作之間的內存可見性,以及什么時候不能重排序。在JMM中如果一個操作執(zhí)行的結果需要對另一個操作可見,那么這兩個操作之間必須要存在nappens-.before:關系。換個角度來說,如果A happens-before B,則意味著A的執(zhí)行結果必須對B可見,也就是保證跨線程的內存可見性。其中,前4條規(guī)則與程序員密切相關。
- 1.程序順序規(guī)則:一個線程中的每個操作,happens-before于(對…可見)該線程中的任意后續(xù)操作,
- 2.volatile?變量規(guī)則:對一個volatile域的寫,happens-before于任意后續(xù)對這個volatile域的讀,
- 3.synchronized規(guī)則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖,
- 4.傳遞性:若A happens-.before B,且B happens-before C,則A happens-before C,
- 5.start()規(guī)則:若線程A執(zhí)行Thread.start(0,則線程A的start()操作nappens-before于線程B中的任意操作,
- 6.join規(guī)則:若線程A執(zhí)行ThreadB.join0并成功返回,那么線程B中的任意操作happens-.before于線程A從ThreadB.join0的成功返回。
(2):關鍵字vlatile
- 4.1 volatile的基本特性
- 可見性:對一個volatile變量的讀,總是能看到對這個volatile變量最后的寫入:
- 原子性:對任意單個volatile變量的讀/寫具有原子性,但類似volatile++這種復合操作不具有原子性。
- 4.2 volatilet的內存語義
- 寫內存語義:當寫一個volatile變量時,JMM會把該線程本地內存中的共享變量的值刷新到主內存
- 讀內存語義:當讀一個volatile變量時,JMM會把該線程本地內存置為無效,使其從主內存中讀取共享變量。
總結
- 上一篇: 利用结构体数组实现重排序(详解)
- 下一篇: java并发之synchronized实