什麼是進程
- 程序(任務)的執行過程。
- 持有資源(共享內存,共享文件)和線程(能夠看作爲載體)。
什麼是線程
- 線程是系統中最小的執行單元。
- 同一個進程中有多個線程。
- 線程共享進程的資源
線程的交互
經常使用方法
-
Thread類經常使用的方法java
-
線程的建立緩存
- Thread()
- Thread(Stirng name)
- Thread(Runnable target)
- Thread(Runnable target,String name)
-
線程的方法安全
- void start() 用於啓動線程
-
線程休眠多線程
- static void sleep(long millis)
- static void sleep(long millis,int nanos)
-
使其餘線程等待,當前線程終止併發
- void join()
- void join(long millis)
- void join(long millis,int nanos)
- static void yield() 當前運行線程釋放處理器資源
-
獲取線程引用函數
- static Thread currentThread() 返回當前運行的線程引用
可見性
一個線程對共享變量值的修改,可以及時地被其餘線程看到
若是一個變量在多個線程的工做內存中都存在副本,那麼這個變量就是這幾個線程的共享變量。
Java內存模型(JMM)
Java內存模型(Java Memory Model)描述了Java程序中各類變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的底層細節。
- 全部的變量都存儲在主內存中
- 每一個線程都有本身獨立的工做內存,裏面保存該線程使用到的變量的副本(主內存中該變量的一份拷貝)
-
兩條規定高併發
- 線程對共享變量的全部操做都必須在本身的工做內存中進行,不能直接從主內存中讀寫
- 不一樣線程之間沒法直接訪問其餘線程工做內存中的變量,線程間變量值的傳遞須要功過主內存來完成。
- 共享變量可見性實現的原理
線程1對共享變量的修改要想被線程2及時看到,必需要通過以下的兩個步驟。
1.把工做內存1中更新過的共享變量刷新到主內存中性能
2.把內存中最新的共享變量的值更新到工做內存2中優化
synchronized實現可見性
synchronized可以實現:
JMM關於synchronized的兩條規定:
- 線程解鎖前,必須把共享變量的最新值刷新到主內存中。
- 線程加鎖時,將清空工做內存中共享變量的值,從而使用共享變量時須要從主內存中從新讀取最新的值(注意:加鎖與解鎖須要的是同一把鎖)
這兩點結合起來,就能夠保證線程解鎖前對共享變量的修改在下次加鎖時對其餘的線程可見,也就保證了線程之間共享變量的可見性。ui
線程執行互斥代碼的過程:
- 得到互斥鎖
- 清空工做內存
- 從主內存拷貝最新副本到工做內存中。
- 執行代碼
- 將更改事後的共享變量的值刷新到主內存中去。
- 釋放互斥鎖。
指令重排序
重排序:代碼書寫的順序與實際執行的順序不一樣,指令重排序是編譯器或處理器爲了提供程序的性能而作的優化
- 編譯器優化的重排序(編譯器優化)
- 指令級並行重排序(處理器優化)
- 內存系統的重排序(處理器優化)
as-if-serial
as-if-serial:不管如何重排序,程序執行的結果應該和代碼順尋執行的結果一致(Java編譯器、運行時和處理器都會保證Java在單線程下遵循as-if-serial語義)
例子:
int num1=1;//第一行
int num2=2;//第二行
int sum=num1+num;//第三行
- 單線程:第一行和第二行能夠重排序,但第三行不行
- 重排序不會給單線程帶來內存可見性問題
- 多線程中程序交錯執行時,重排序可能會照成內存可見性問題。
可見行分析:
致使共享變量在線程間不可見的緣由:
- 線程的交叉執行
- 重排序結合線程交叉執行
- 共享變量更新後的值沒有在工做內存與主內存間及時更新
volatile實現可見性
volatile關鍵字:
- 可以保證volatile變量的可見性
- 不能保證volatile變量的原子性
volatile如何實現內存可見性:
深刻來講:經過加入內存屏障和禁止重排序優化來實現的。
volatile如何實現內存可見性:
通俗的講:volatile變量在每次被線程訪問時,都強迫從主內存中重讀該變量的值,而當變量發生變化時,又強迫線程將最新的值刷新到主內存。這樣任什麼時候刻,不一樣的線程總能看到該變量的最新的值。
線程寫volatile變量的過程:
- 改變線程工做內存中volatile變量副本的值。
- 將改變後的副本的值從工做內存刷新到主內存。
線程讀volatile變量的過程:
- 從主內存中讀取最新的volatile變量的值到工做內存中。
- 從工做內存中讀取volatile變量的副本。
volatile不能保證volatile變量複合操做的原子性:
private int number=0;//原子性操做
number++;//不是原子性操做
number++的步驟能夠分爲三步:
- 讀取number的值
- 將number的值加1
- 寫入最新的number的值
若是使用synchronized關鍵字:
//加入synchronized關鍵字後能夠number++的步驟變成原子操做。
synchronized(this){
number++;
}
加入synchronized關鍵字後能夠number++的步驟變成原子操做。
保證方法操做的原子性
解決方案:
- 使用synchronized關鍵字
- 使用ReentrantLock(java.until.concurrent.locks包下)
- 使用AtomicInterger(vava,util.concurrent.atomic包下)
volatile的適用場景
要在多線程總安全的使用volatile變量,必須同時知足:
-
對變量的寫入操做不依賴其當前值
- 不知足:number++、count=count*5
- 知足:boolean變量、記錄溫度變化的變量等
-
該變量沒有包含在具備其餘變量的不變式中
synchronized和volatile的比較;
- synchronized鎖住的是變量和變量的操做,而volatile鎖住的只是變量,並且該變量的值不能依賴它自己的值,volatile算是一種輕量級的同步鎖
- volatile不須要加鎖,比synchronized更加輕量級,不會阻塞線程。
- 從內存可見性角度講,volatile讀至關於加鎖,volatilexie至關於解鎖。
- synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,沒法保證原子性。
注:因爲voaltile比synchronized更加輕量級,因此執行的效率確定是比synchroized更高。在能夠保證原子性操做時,能夠儘可能的選擇使用volatile。在其餘不能保證其操做的原子性時,再去考慮使用synchronized。
補充內容
- 問:即便沒有保證可見性的措施,不少時候共享變量依然可以在主內存和工做內存中及時的更新
通常只有在短期內高併發的狀況下才會出現變量得不到及時更新的狀況,由於cpu在執行時會很快的刷新緩存,因此通常狀況下很難看到這種問題。
- 對64位(long、double)變量的讀寫可能不是原子操做:
Java內存模型容許JVM將沒有被volatile修飾的64位數據類型讀寫操做劃分爲兩次32位的讀寫操做來進行,這就會致使有可能讀取到「半個變量」的狀況,解決辦法就是加上volatile關鍵字。
- final也能夠保證線程之間內存變量的可見性。
Final 變量在併發當中,原理是經過禁止cpu的指令集重排序,具體能夠在
重排序詳解1、
重排序詳解2,來提供現成的可見性,來保證對象的安全發佈,防止對象引用被其餘線程在對象被徹底構造完成前拿到並使用。
與鎖和volatile相比較,對final域的讀和寫更像是普通的變量訪問。對於final域,編譯器和處理器要遵照兩個重排序規則:
- 在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操做之間不能重排序。
- 初次讀一個包含final域的對象的引用,與隨後初次讀這個final域,這兩個操做之間不能重排序。
與Volatile 有類似做用,不過Final主要用於不可變變量(基本數據類型和非基本數據類型),進行安全的發佈(初始化)。而Volatile能夠用於安全的發佈不可變變量,也能夠提供可變變量的可見性。