1.Java內(nèi)存模型
JMM的內(nèi)存模型如圖所示屈暗,其規(guī)定了所有變量都存儲在主內(nèi)存中据沈,每條線程還有自己的工作內(nèi)存承边,工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝律胀,線程對變量的所有操作,都必須在工作內(nèi)存中進行遇八。
三大特性:
- 可見性:指當一個線程修改了共享變量的值瘦赫,其他線程能夠立即得知這個修改箕般。JMM是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值來實現(xiàn)可見性的顽分。
- 原子性:不可分割徐许,某個線程在做具體某個事務(wù)時,中間不可以被加塞或分割卒蘸,需要整體完整雌隅。
- 有序性:在本線程內(nèi)觀察,所有操作都是有序的悬秉。在一個線程觀察另一個線程澄步,所有操作都是無序的冰蘑,無序是因為發(fā)生了指令重排序和泌。
2.volatitle
volatitle是jvm提供的輕量級的同步機制,保證可見性祠肥,但不保證完整性武氓,禁止指令重排,其并不是并發(fā)安全的仇箱。由于其只保證可見性县恕,在不符合以下兩條規(guī)則的場景中,仍然要通過加鎖來保證原子性:
- 運算結(jié)果并不依賴變量的當前值剂桥,或者能夠確保只有單一的線程修改變量的值忠烛。
- 變量不需要與其他的狀態(tài)變量共同參與不變約束。
可見性保證:volatile保證了修飾的共享變量在轉(zhuǎn)換為匯編語言時权逗,會加上一個lock為前綴的指令美尸,當CPU發(fā)現(xiàn)這個指令時,會立刻做以下兩件事:
1.將當前內(nèi)核中線程工作內(nèi)存中該共享變量刷新到主存
2.通知其他內(nèi)核里緩存的該共享變量內(nèi)存地址無效
3.狀態(tài)切換
Java語言定義了5種線程狀態(tài)斟薇,在任意一個時間點师坎,一個線程只能有且只有一種狀態(tài):
- 新建(New):創(chuàng)建后尚未啟動的線程處于這種狀態(tài)
- 運行(Runnable):處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著CPU給它分配時間
- 無限期等待(Waiting):處于這種狀態(tài)的線程不會被CPU分配時間堪滨,它們要等待被其他線程喚醒胯陋。可通過以下方法無限期等待:
1.沒有設(shè)置Timeout參數(shù)的Object.wait()
2.沒有設(shè)置Timeout參數(shù)的Thread.join()
3.LockSupport.park()方法 - 限期等待(Timed Waiting):這種狀態(tài)下也不會被分配CPU時間袱箱,不過無須等待被喚醒遏乔,一段時間后會由系統(tǒng)自動喚醒。
- 阻塞(Blocked):線程在等待著獲取到一個排他鎖发笔。
- 結(jié)束(Terminated):已終止的線程狀態(tài)盟萨。
4.線程安全
當多個線程訪問一個對象時,如果不用考慮這些線程在運行時環(huán)境下的調(diào)度和交替執(zhí)行筐咧,也不需要進行額外的同步鸯旁,或者在調(diào)用方進行任何其他的協(xié)調(diào)操作噪矛,調(diào)用這個對象的行為都可以獲得正確的結(jié)果,那這個對象是線程安全的铺罢。
5.線程安全的實現(xiàn)方法
5.1 互斥同步
同步是指多個線程并發(fā)訪問共享數(shù)據(jù)時艇挨,保證共享數(shù)據(jù)在同一時刻只被一個(或者是一些,使用信號量的時候)線程使用韭赘。而互斥是實現(xiàn)同步的一種手段缩滨,臨界區(qū)、互斥量和信號量都是主要的互斥實現(xiàn)方式泉瞻。
-
synchronized
synchronized是JVM實現(xiàn)的脉漏,可用于同步代碼塊,同步一個方法袖牙,同步一個類侧巨,同步一個靜態(tài)方法。JVM基于進入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步鞭达。代碼塊同步是使用monitorenter和monitorexit指令實現(xiàn)的司忱,而方法同步卻不是。
Java的線程是映射到操作系統(tǒng)的原生線程智商的畴蹭,如果要阻塞或喚醒一個線程坦仍,都需要操作系統(tǒng)來幫忙完成,這就需要從用戶態(tài)切換到內(nèi)核態(tài)種叨襟,因此狀態(tài)轉(zhuǎn)換你需要耗費很多的處理器時間繁扎,所以synchronized是Java語言的一個重量級操作。 - synchronized的優(yōu)化
- 自旋鎖:互斥同步進入阻塞狀態(tài)的開銷很大糊闽。自旋鎖讓一個線程請求一個共享數(shù)據(jù)的鎖的時候執(zhí)行忙循環(huán)一段時間梳玫,如果在這段時間能獲得鎖,就可以避免進入阻塞狀態(tài)墓怀。它只適用于共享數(shù)據(jù)鎖定狀態(tài)很短的情況汽纠。
- 鎖消除:被檢測出不可能存在競爭的共享數(shù)據(jù)的鎖進行消除。
- 鎖粗化:如果虛擬機探測到一系列連續(xù)的操作對同一個對象頻繁加鎖傀履,就會把加鎖的范圍擴展到整個操作序列的外部虱朵。
- 輕量級鎖:相對于傳統(tǒng)的重量級鎖而言,使用CAS操作來避免重量級鎖使用互斥量的開銷钓账。先采用CAS操作進行同步碴犬,如果CAS失敗了再改用互斥量來進行同步。
- 偏向鎖:讓第一個獲取鎖對象的進程梆暮,在這之后獲取該鎖就不再需要進行同步操作服协。當有另外一個鎖去嘗試獲取這個對象時,偏向狀態(tài)就宣告結(jié)束啦粹,此時恢復(fù)到無鎖狀態(tài)或輕量級鎖狀態(tài)偿荷。
ReentrantLock
基本語法上和synchronized相似窘游,都是可重入鎖,但增加了一些高級功能跳纳,比如等待可中斷忍饰、可實現(xiàn)公平鎖、鎖可以綁定多個條件寺庄。二者的區(qū)別
- synchronized基于jvm實現(xiàn)艾蓝,ReentrantLock基于jdk實現(xiàn)
- ReentranLock是等待可中斷的,synchronized不是
- ReentranLock可實現(xiàn)公平鎖
- ReentranLock可綁定多個條件斗塘。
5.2 非阻塞同步
從處理問題的方式上說赢织,互斥同步屬于一種悲觀的并發(fā)策略,總是認為只要不去做正確的同步措施馍盟,就會出現(xiàn)問題于置,無論是否出現(xiàn)競爭,都要進行加鎖朽合。而樂觀鎖是先進行操作俱两,如果沒有其他線程競爭共享數(shù)據(jù),那么操作就成功了曹步。這種樂觀的并發(fā)策略不需要把線程掛起。
-
CAS
比較并交換休讳。CAS指令有3個操作數(shù)讲婚,內(nèi)存地址V,舊的期望值A(chǔ)俊柔,新的值B筹麸。只有當V的值等于A,才把V的值更新為B雏婶。 -
JUC中的原子類
例如AtomicInteger中就調(diào)用了Unsafe類的CAS操作物赶。其自增操作就是基于CAS實現(xiàn)的。 -
ABA
如果一個變量初次讀取的時候是A值留晚,后來被改為了B酵紫,然后又被改回A,那CAS會誤認為它沒有改變過错维,這就會導(dǎo)致ABA問題奖地。JUC提供了一個帶有標記的原子引用類AtomicStampedReference來解決這個問題,它可以通過控制變量值的版本來保證CAS的正確性赋焕。
6.AQS(AbstractQueuedSynchronizer隊列同步器)
AQS是一個用來構(gòu)建鎖和同步器的框架参歹,ReentrantLock,Semaphore隆判,F(xiàn)utureTask等等均是基于AQS犬庇。其核心思想是僧界,如果被請求的共享資源空閑,則將當前請求資源的線程設(shè)置為有效的工作線程臭挽,并且將共享資源設(shè)置為鎖定狀態(tài)捎泻。如果被請求的共享資源被占用,需要一套線程阻塞等待以及喚醒時鎖分配的機制埋哟,這個機制是用CLH隊列鎖實現(xiàn)的笆豁,即將暫時獲取不到鎖的線程加入到隊列中。
CLH是一個虛擬的雙向隊列(不存在隊列實例赤赊,僅存在結(jié)點間的關(guān)聯(lián)聯(lián)系)闯狱。AQS將每條請求共享資源的線程封裝成一個CLH鎖隊列的一個結(jié)點來實現(xiàn)鎖的分配。
- AQS定義兩種資源共享方式
Exclusive(獨占):只有一個線程能執(zhí)行抛计,如可重入鎖
Share(共享):多個線程同時執(zhí)行哄孤,如Semaphore/CountDownLatch。
同步器的設(shè)計是基于模板方法模式的吹截,也就是說瘦陈,使用者需要繼承同步器并重寫指定的方法。
同步器可重寫的方法
- AQS組件
- Semaphone:允許多個線程同時訪問資源波俄。
- CountDownLatch:用來控制一個線程等待多個線程晨逝。維護了一個計數(shù)器 cnt,每次調(diào)用 countDown() 方法會讓計數(shù)器的值減 1懦铺,減到 0 的時候捉貌,那些因為調(diào)用 await() 方法而在等待的線程就會被喚醒。
- CyclicBarrier:用來控制多個線程互相等待冬念,只有當多個線程都到達時趁窃,這些線程才會繼續(xù)執(zhí)行。
- ReentrantReadWriteLock:讀寫鎖允許同時對某一資源進行讀
7.ThreadLocal
ThreadLocal對象可以提供線程局部變量急前,每個線程Thread擁有一份自己的副本變量醒陆,多個線程互不干擾。每個線程都有一個ThreadLocalMap裆针,其結(jié)構(gòu)類似HashMap刨摩,但ThreadLocalMap中沒有鏈表結(jié)構(gòu)。每一個ThreadLocal對象作為Map中的key据块,value為代碼中放入的值码邻。
其中的key為弱引用,發(fā)生GC后另假,key會被回收像屋,而value為強引用,不會被回收边篮,這個時候就可能導(dǎo)致內(nèi)存泄漏己莺。ThreadLocalMap的解決方法為再調(diào)用set()奏甫、get()、remove()方法的時候凌受,會清理掉key為Null的記錄阵子。
8.線程池
http://www.reibang.com/p/3f6eed342491
9.場景
- 死鎖
public static Object lock1=new Object();
public static Object lock2=new Object();
public static void main(String[] args) {
Thread thread1=new Thread(()->{
synchronized (lock1){
System.out.println("Thread 1 get Lock 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
synchronized (lock2){
System.out.println("Thread 1 get Lock 2");
}
}
}
});
Thread thread2=new Thread(()->{
synchronized (lock2){
System.out.println("Thread 2 get Lock 2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
synchronized (lock1){
System.out.println("Thread 2 get Lock 1");
}
}
}
});
thread1.start();
thread2.start();
}
- 兩個線程交替打印A和B
public static volatile boolean flag=true;
public static void main(String[] args) {
Thread thread1=new Thread(() -> {
while (true){
if (flag){
System.out.println("A");
flag=!flag;
}
}
});
Thread thread2=new Thread(() -> {
while (true){
if (!flag){
System.out.println("B");
flag=!flag;
}
}
});
thread1.start();
thread2.start();
}