零零碎碎的東西老是記不長久,僅僅學習別人的文章也只是他人咀嚼後留下的殘渣。無心中發現了這個每日一道面試題,想了想若是隻是簡單地去思考,那麼不只會收效甚微,甚至難一點的題目本身可能都懶得去想,堅持不下來。因此不如把每一次的思考、理解以及別人的看法記錄下來。不只加深本身的理解,更要激勵本身堅持下去。java
在介紹多線程中的同步以前,咱們先來了解下併發編程。git
//線程1:
context = loadContext(); //語句1
inited = true; //語句2
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
複製代碼
線程1中語句1和語句2沒有數據依懶性,inited僅是一個標記變量,因此這兩個語句可能發生指令重排序。當語句2在語句1以前執行時,這是剛好線程2啓動,標記變量init爲true則線程2認爲初始化已經完成,而此時語句1並無執行,就會形成問題。github
因此說,併發編程中保證原子性、可見性、有序性,是同步的基本要素面試
volatile是java的一個關鍵字,一旦一個共享變量(類的成員變量、靜態變量)被volatile關鍵字修飾,就具有有兩層含義算法
這就保證了可見性與有序性,可是volatile並不保證可見性。看下面一段代碼編程
public class Main {
private volatile static int test = 0;
private volatile static int count = 10;
public static void main(String[] args) {
for(int i=0;i<10;i++){
Main mm = new Main();
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0;j<10000;j++){
mm.increase();
}
count--;
}
}).start();
}
while (count > 0){}//全部線程執行完畢
System.out.println("最後的數據爲" + test);
}
private void increase(){test++;}
}
複製代碼
運行後你會發現,每一次的結果都小於100000。這是由於test++
這個操做,它不是原子性的,與test自己這個變量無關。數組
test++
通過三個原子操做,讀取test變量值、test變量進行加一操做、將操做後的變量值寫入工做內存。當線程1執行到前兩步時,線程2開始讀取test變量值,當線程1三個步驟執行完畢時,雖然此時test的值會立馬更新到線程2,可是線程2已經在此以前進行了讀取變量值的操做,因此實際上兩個線程只讓test加了一次。緩存
因此說,volatile只進行一些簡單的同步操做,好比上面提到的標記變量安全
volatile boolean inited = false;
//線程1:
context = loadContext(); //語句1
inited = true; //語句2
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
複製代碼
併發編程中的單例模式性能優化
class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
複製代碼
這個雖然有synchronized關鍵字來保證單線程訪問,可是這裏面實際上是instance=new Singleton()
指令重排序的問題,這一步有三個原子性操做
synchronized一樣是java中的一個關鍵字。它經過鎖機制實現同步,要執行代碼,則必需要得到鎖,只有得到鎖對象的線程才能執行鎖住的代碼,其餘線程沒有得到鎖只能阻塞。鎖有對象鎖和類鎖。同步有兩種表現形式:同步代碼塊和同步方法
對象鎖
class Test{
public void testMethod(){
synchronized(this){
...
}
}
}
複製代碼
類鎖
class Test{
public void testMethod(){
synchronized(Test.class){
...
}
}
}
複製代碼
對象鎖。這裏的o表明任意一個object對象或者數組,誰擁有這個對象誰就能夠執行該程序塊代碼
class Test{
public void testMethod(){
synchronized(o){
...
}
}
}
複製代碼
類鎖
class Test{
public synchronized static void testMethod(){
...
}
}
複製代碼
對象鎖
class Test{
public synchronized void testMethod(){
...
}
}
複製代碼
ReentrantLock是一個類,它的同步方法與synchronized大體相同。
基本用法
ReentrantLock lock = new ReentrantLock(); //參數默認false,不公平鎖
.....................
lock.lock(); //若是被其它資源鎖定,會在此等待鎖釋放,達到暫停的效果
try {
//操做
} finally {
lock.unlock(); //釋放鎖
}
複製代碼
ReentrantLock經過lock方法與unlock方法顯式的獲取鎖與釋放鎖,與synchronized隱式的獲取鎖不一樣。當線程執行到lock.lock()方法時,會嘗試獲取鎖,獲取到鎖則執行下去,獲取不到則會阻塞。unlock()方法則會釋放當前線程所持有的鎖,若是沒有鎖能夠釋放可能會發生異常。
顯式的獲取鎖雖然比隱式的自動獲取鎖麻煩了很多,但多了許多可控制的狀況。咱們能夠中斷獲取鎖、延遲獲取鎖等一些操做。
公平鎖
當許多線程在隊列中等待鎖時,cpu會隨機挑選一個線程得到鎖。這樣就會出現飢餓現象,即優先級低的線程不斷被優先級高的線程搶佔鎖資源,以致於很長時間得到不到鎖,這就是不公平鎖。RenntrantLock可使用公平鎖,即cpu調度按照線程前後等待的順序得到鎖,避免飢餓現象。可是執行效率會比較低,由於須要維護一個有序隊列。synchronized是不公平鎖。
ReentrantLock lock = new ReentrantLock(true);
複製代碼
經過在建立對象時傳入boolean對象表示使用什麼鎖,默認爲false不公平鎖。
能夠看出,ReentrantLock實現了許多更高級的功能,不過卻多了點複雜性。在性能上來講,競爭不激烈時,二者的性能是差很少的,不過當競爭激烈時,即有大量線程等待獲取鎖,ReentrantLock的性能要更好一些,具體的使用看狀況進行。
jdk1.6之前synchronized的性能是不好的,jdk1.6之後對synchronized的性能優化了很多,和ReentrantLock性能差不了多少。官方也表示更支持synchronized,之後還有優化的餘地,因此在都能符合需求的狀況下,推薦使用synchronized。
樂觀鎖與悲觀鎖:
cpu調度線程,經過將時間片分配給不一樣的線程進行調度。時間片的切換也就是線程的切換,須要清除寄存器、緩存數據,切換後加載線程須要的數據,須要耗費必定的時間。線程阻塞後,經過notify、notifyAll喚醒。假如線程1在嘗試獲取鎖,獲取失敗,掛起。這時鎖被釋放,線程1被喚醒,嘗試獲取鎖,結果又被其餘線程搶佔鎖,線程1繼續掛起,獲取鎖的線程只佔用鎖很短的時間,釋放鎖,線程1又被喚醒。。。就這樣,線程1反覆的掛起、喚醒,線程1認爲其餘線程獲取鎖就必定會對鎖內的資源進行更新等操做,因此不斷等待,這就是悲觀鎖。synchronized這種獨佔鎖就是悲觀鎖。
樂觀鎖並不加鎖,首先會認爲在本身修改資源以前其餘線程不會對資源進行更新等操做,它會嘗試用鎖內資源進行本身的操做,若是修改後的數據發生衝突,就會放棄以前的操做。就這樣一直循環,知道操做成功。
CAS就是一種樂觀鎖的概念,內有三個操做數---內存原值(C)、預期舊值(A)、新值(B),當且只當內存原值與預期舊值的結果同樣時,才更新新值。否則就是不斷地循環嘗試。Java中java.util.concurrent.atomic包相關類就是 CAS的實現.
類名 | 說明 |
---|---|
AtomicBoolean | 能夠用原子方式更新的 boolean 值。 |
AtomicInteger | 能夠用原子方式更新的 int 值。 |
AtomicIntegerArray | 能夠用原子方式更新其元素的 int 數組。 |
AtomicIntegerFieldUpdater | 基於反射的實用工具,能夠對指定類的指定 volatile int 字段進行原子更新。 |
AtomicLong | 能夠用原子方式更新的 long 值。 |
AtomicLongArray | 能夠用原子方式更新其元素的 long 數組。 |
AtomicLongFieldUpdater | 基於反射的實用工具,能夠對指定類的指定 volatile long 字段進行原子更新。 |
AtomicMarkableReference | AtomicMarkableReference 維護帶有標記位的對象引用,能夠原子方式對其進行更新。 |
AtomicReference | 能夠用原子方式更新的對象引用。 |
AtomicReferenceArray | 能夠用原子方式更新其元素的對象引用數組。 |
AtomicReferenceFieldUpdater | 基於反射的實用工具,能夠對指定類的指定 volatile 字段進行原子更新。 |
AtomicStampedReference AtomicStampedReference | 維護帶有整數「標誌」的對象引用,能夠用原子方式對其進行更新。 |
這種不須要鎖的非阻塞算法,在性能上是要優於阻塞算法。通常使用以下,實現自增i++
的同步操做
public class Test {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}
複製代碼
CAS的問題