參考資料:
Av86373641:黑馬程序員 - 全面深入學(xué)習(xí)java并發(fā)編程
-
進(jìn)程和線程
并行并發(fā)
并發(fā):同一個(gè)時(shí)間段交叉運(yùn)行多個(gè)線程
并行:同一個(gè)時(shí)間點(diǎn)運(yùn)行多個(gè)線程線程的創(chuàng)建方式
- 覆蓋run方法
- 編寫一個(gè)Runnable接口對(duì)象然后給到Thread對(duì)象
- FutureTask和Callable接口(Callable比Runnable多一個(gè)返回值,并且可以拋出異常),可以返回值,F(xiàn)utureTask創(chuàng)建完也要給到Thread,然后調(diào)用Thread就可以,因?yàn)镕utureTask也實(shí)現(xiàn)了Runnable接口。我們看下FutureTask對(duì)runnable的實(shí)現(xiàn)就可以知道,它是調(diào)用了Callable對(duì)象,處理了異常和返回值。
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
前兩者推薦推薦用第二種
方法1 是把線程和任務(wù)合并在了一起,方法2 是把線程和任務(wù)分開了
用 Runnable 更容易與線程池等高級(jí) API 配合
用 Runnable 讓任務(wù)類脫離了 Thread 繼承體系,更靈活
// 構(gòu)造方法的參數(shù)是給線程指定名字,推薦
Thread t1 = new Thread("t1") {
@Override
// run 方法內(nèi)實(shí)現(xiàn)了要執(zhí)行的任務(wù)
public void run() {
log.debug("hello");
}
};
t1.start();
// 創(chuàng)建任務(wù)對(duì)象
Runnable task2 = () -> log.debug("hello");
// 參數(shù)1 是任務(wù)對(duì)象; 參數(shù)2 是線程名字,推薦
Thread t2 = new Thread(task2, "t2");
t2.start();
Thread和Runnable的關(guān)系
有了Runnable可以把線程和任務(wù)分開來,Runnable可以更容易與線程池等高級(jí)API配合,更加靈活。棧幀
棧,每個(gè)函數(shù)有一個(gè)棧幀
每個(gè)棧由多個(gè)棧幀(Frame)組成,對(duì)應(yīng)著每次方法調(diào)用時(shí)所占用的內(nèi)存
每個(gè)線程只能有一個(gè)活動(dòng)棧幀,對(duì)應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法
- Thread方法
join:調(diào)用的線程等待被調(diào)用的線程運(yùn)行結(jié)束,主要用于同步。
yield:讓出當(dāng)前線程,從running到runnable
interrupt:打斷其他線程的運(yùn)行
如果被打斷線程正在 sleep,wait,join 會(huì)導(dǎo)致被打斷的線程拋出 InterruptedException,并清除 打斷標(biāo)記 如果打斷的正在運(yùn)行的線程,則會(huì)設(shè)置 打斷標(biāo)記 ;park 的線程被打斷,也會(huì)設(shè)置 打斷標(biāo)記
- 守護(hù)線程
總結(jié):依附其他現(xiàn)成的存在而存在,比如垃圾回收器
默認(rèn)情況下,Java 進(jìn)程需要等待所有線程都運(yùn)行結(jié)束,才會(huì)結(jié)束。有一種特殊的線程叫做守護(hù)線程,只要其它非守
護(hù)線程運(yùn)行結(jié)束了,即使守護(hù)線程的代碼沒有執(zhí)行完,也會(huì)強(qiáng)制結(jié)束。
-
java的線程狀態(tài)
waiting是join的時(shí)候的狀態(tài)
blocked是等待鎖的時(shí)候的狀態(tài) 臨界區(qū)
一個(gè)程序運(yùn)行多個(gè)線程本身是沒有問題的
問題出在多個(gè)線程訪問共享資源
多個(gè)線程讀共享資源其實(shí)也沒有問題
在多個(gè)線程對(duì)共享資源讀寫操作時(shí)發(fā)生指令交錯(cuò),就會(huì)出現(xiàn)問題
一段代碼塊內(nèi)如果存在對(duì)共享資源的多線程讀寫操作,稱這段代碼塊為臨界區(qū)臨界區(qū)出問題的解決方案
阻塞式的解決方案:synchronized,Lock
非阻塞式的解決方案:原子變量
- synchronized的兩種用法
雖然 java 中互斥和同步都可以采用 synchronized 關(guān)鍵字來完成,但它們還是有區(qū)別的:
互斥是保證臨界區(qū)的競(jìng)態(tài)條件發(fā)生,同一時(shí)刻只能有一個(gè)線程執(zhí)行臨界區(qū)代碼
同步是由于線程執(zhí)行的先后、順序不同、需要一個(gè)線程等待其它線程運(yùn)行到某個(gè)點(diǎn)
synchronized用于互斥
要注意兩個(gè)都要枷鎖,并且要鎖住同一個(gè)對(duì)象。成員變量和靜態(tài)變量是否線程安全?
如果它們沒有共享,則線程安全
如果它們被共享了,根據(jù)它們的狀態(tài)是否能夠改變,又分兩種情況
如果只有讀操作,則線程安全
如果有讀寫操作,則這段代碼是臨界區(qū),需要考慮線程安全局部變量是否線程安全?(重點(diǎn)關(guān)注P66)
局部變量是線程安全的
但局部變量引用的對(duì)象則未必
如果該對(duì)象沒有逃離方法的作用訪問,它是線程安全的
如果該對(duì)象逃離方法的作用范圍,需要考慮線程安全常見線程安全類
String
Integer
StringBuffer
Random
Vector
Hashtable
java.util.concurrent 包下的類線程安全類方法的組合
Hashtable table = new Hashtable();
// 線程1,線程2
if( table.get("key") == null) {
table.put("key", value);
}
開閉原則
閉原則(final private)可以增加類的安全性,比如String設(shè)置為final就是。(P66)-
monitor鎖,管程/監(jiān)視器
鎖優(yōu)化(這些措施都是java虛擬機(jī)自動(dòng)操作的,雖然可能可以配置)
上面的monitor是重型鎖,還有輕量鎖和偏向鎖。
偏向鎖:這個(gè)鎖的前提是鎖真的主要是其中一個(gè)線程在用。
synchronized最開始加的是輕量級(jí)鎖,后面有人來了,進(jìn)行鎖膨脹才加為重量級(jí)鎖,鎖膨脹是為了后面的線程有等待隊(duì)列。
鎖膨脹:輕量級(jí)鎖變?yōu)橹亓考?jí)鎖,鎖膨脹是為了后面的線程有等待隊(duì)列。
自旋優(yōu)化:空轉(zhuǎn)檢查,避免因?yàn)檫M(jìn)入阻塞隊(duì)列帶來的上下文切換,多核CPU才有用。自旋失敗的時(shí)候才進(jìn)行阻塞。單核的時(shí)候其實(shí)是利用任務(wù)隊(duì)列當(dāng)做隊(duì)列,單核其實(shí)就很不好,因?yàn)樽孕齼?yōu)化就是為了減少任務(wù)切換。
偏向鎖:輕量級(jí)鎖在沒有競(jìng)爭(zhēng)的時(shí)候,每次重入仍需要執(zhí)行CAS操作。在對(duì)象一開始的時(shí)候就是使用的偏向鎖,如果后面有任意一次競(jìng)爭(zhēng)或者偏向的改變,即使解鎖了,重新加鎖,都不是偏向鎖了?;蛘哒{(diào)用wait,notify的時(shí)候也會(huì)被撤銷,終身禁用。因?yàn)閣ait,notify只有重量級(jí)鎖才有。
批量重偏向:如果發(fā)現(xiàn)在t2線程內(nèi)因?yàn)槠虿煌鴱钠蜴i轉(zhuǎn)向輕量級(jí)鎖太多之后(即撤銷偏向鎖),后面把這些對(duì)象和鎖都偏向于t2線程。
批量撤銷:如果撤銷偏向鎖的次數(shù)太多,那么后面對(duì)于同一個(gè)類的對(duì)象再也不用偏向鎖了。
鎖消除:JIT即時(shí)編譯器會(huì)優(yōu)化,如果非必要會(huì)消除鎖。-
wait¬ify
進(jìn)入synchronized代碼片段之后(記住wait,notify調(diào)用的這個(gè)必要條件),調(diào)用wait的線程會(huì)進(jìn)入waitset進(jìn)行等待,并且放棄鎖,這時(shí)處于waiting狀態(tài),等待notify/notifyAll后進(jìn)入EntryList等待調(diào)度,這時(shí)處于blocked狀態(tài)。這意味著A在wait之后,B進(jìn)入synchronized代碼,B調(diào)用notify,A還是不會(huì)馬上執(zhí)行,至少得等待B退出synchronized代碼(或者調(diào)用wait放棄鎖),因?yàn)锳在EntryList等待鎖,而B沒有退出synchronized代碼就沒有釋放鎖。
wait,notify都必須在sychronized里面才可以。
兩者的使用:wait表示要滿足一定的條件,notify表示條件已經(jīng)滿足 wait¬ify使用搭配
synchronized(lock) {
while(條件不成立) {
lock.wait();
}
// 干活
}
//另一個(gè)線程
synchronized(lock) {
lock.notifyAll();
}
- join的實(shí)現(xiàn)
join是用wait實(shí)現(xiàn)的,wait等待線程死掉。
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
park&unpark
與 Object 的 wait & notify 相比
wait,notify 和 notifyAll 必須配合 Object Monitor 一起使用,而 park,unpark 不必
park & unpark 是以線程為單位來【阻塞】和【喚醒】線程,而 notify 只能隨機(jī)喚醒一個(gè)等待線程,notifyAll
是喚醒所有等待線程,就不那么【精確】
park & unpark 可以先 unpark,而 wait & notify 不能先 notify-
ReentrantLock可重入鎖
lock:得不到鎖死等。
lockInterruptly可打斷:在獲取鎖的時(shí)候可以打斷,退出阻塞,得不到鎖而返回。
tryLock:得不到鎖立即返回,或者可以設(shè)置超時(shí)時(shí)間。
synchronized只能是死等,相當(dāng)于只是lock。
但是synchronized會(huì)自動(dòng)釋放鎖,包括發(fā)生異常的時(shí)候。 Synchronized和ReentrantLock等價(jià)代碼
Synchronized{臨界代碼段}
ReentrantLock lock = new ReentrantLock();
lock.lock ();
try {
臨界代碼段
}finally{
lock.unlock();
}Java 內(nèi)存模型
JMM 即 Java Memory Model,它定義了主存、工作內(nèi)存抽象概念,底層對(duì)應(yīng)著 CPU 寄存器、緩存、硬件內(nèi)存、CPU 指令優(yōu)化等。
JMM 體現(xiàn)在以下幾個(gè)方面
原子性 - 保證指令不會(huì)受到線程上下文切換的影響
可見性 - 保證指令不會(huì)受 cpu 緩存的影響
有序性 - 保證指令不會(huì)受 cpu 指令并行優(yōu)化的影響volatile
保證可見性和有序性,并不能保證原子性。synchronized三者都可以。volatile原理
volatile 的底層實(shí)現(xiàn)原理是內(nèi)存屏障,Memory Barrier(Memory Fence)
對(duì) volatile 變量的寫指令后會(huì)加入寫屏障
對(duì) volatile 變量的讀指令前會(huì)加入讀屏障
可見性:
寫屏障(sfence)保證在該屏障之前的,對(duì)共享變量的改動(dòng),都同步到主存當(dāng)中
而讀屏障(lfence)保證在該屏障之后,對(duì)共享變量的讀取,加載的是主存中最新數(shù)據(jù)
有序性:
寫屏障會(huì)確保指令重排序時(shí),不會(huì)將寫屏障之前的代碼排在寫屏障之后
讀屏障會(huì)確保指令重排序時(shí),不會(huì)將讀屏障之后的代碼排在讀屏障之前happens-before
happens-before 規(guī)定了對(duì)共享變量的寫操作對(duì)其它線程的讀操作可見,它是可見性與有序性的一套規(guī)則總結(jié),拋開以下 happens-before 規(guī)則,JMM 并不能保證一個(gè)線程對(duì)共享變量的寫,對(duì)于其它線程對(duì)該共享變量的讀可見。
-
無鎖并發(fā)(樂觀鎖)
CAS:compare and save
-
CAS
變量必須用volatile修飾,不然不能保證獲得最新的
為什么無鎖效率比較高
上下文切換的損耗比較高。
無鎖情況下,即使重試失敗,線程始終在高速運(yùn)行,沒有停歇,而 synchronized 會(huì)讓線程在沒有獲得鎖的時(shí)候,發(fā)生上下文切換,進(jìn)入阻塞。打個(gè)比喻,線程就好像高速跑道上的賽車,高速運(yùn)行時(shí),速度超快,一旦發(fā)生上下文切換,就好比賽車要減速、熄火,等被喚醒又得重新打火、啟動(dòng)、加速... 恢復(fù)到高速運(yùn)行,代價(jià)比較大
但無鎖情況下,因?yàn)榫€程要保持運(yùn)行,需要額外 CPU 的支持,CPU 在這里就好比高速跑道,沒有額外的跑道,線程想高速運(yùn)行也無從談起,雖然不會(huì)進(jìn)入阻塞,但由于沒有分到時(shí)間片,仍然會(huì)進(jìn)入可運(yùn)行狀態(tài),還是會(huì)導(dǎo)致上下文切換。CAS特點(diǎn)
結(jié)合 CAS 和 volatile 可以實(shí)現(xiàn)無鎖并發(fā),適用于線程數(shù)少、多核 CPU 的場(chǎng)景下。
CAS 是基于樂觀鎖的思想:最樂觀的估計(jì),不怕別的線程來修改共享變量,就算改了也沒關(guān)系,我吃虧點(diǎn)再重試唄。
synchronized 是基于悲觀鎖的思想:最悲觀的估計(jì),得防著其它線程來修改共享變量,我上了鎖你們都別想改,我改完了解開鎖,你們才有機(jī)會(huì)。
CAS 體現(xiàn)的是無鎖并發(fā)、無阻塞并發(fā),請(qǐng)仔細(xì)體會(huì)這兩句話的意思
因?yàn)闆]有使用 synchronized,所以線程不會(huì)陷入阻塞,這是效率提升的因素之一
但如果競(jìng)爭(zhēng)激烈,可以想到重試必然頻繁發(fā)生,反而效率會(huì)受影響不可變類
不可變類可以解決資源共享的問題-
內(nèi)存模型
-
可見性
-
有序性
指令優(yōu)化的時(shí)候會(huì)進(jìn)行重排,但是有些重排在多線程的情況下會(huì)出錯(cuò)。
-
如何保證可見性
-
double-check locking
-
happens-before
享元模式flyway
wikipedia: A flyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects-
線程池繼承體系
Scheduled修飾的線程池表示這個(gè)線程池有定時(shí)執(zhí)行等功能。
ThreadPoolExecutor參數(shù)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize 核心線程數(shù)目 (最多保留的線程數(shù))
maximumPoolSize 最大線程數(shù)目
keepAliveTime 生存時(shí)間 - 針對(duì)救急線程
unit 時(shí)間單位 - 針對(duì)救急線程
workQueue 阻塞隊(duì)列
threadFactory 線程工廠 - 可以為線程創(chuàng)建時(shí)起個(gè)好名字
handler 拒絕策略
下面幾個(gè)是基于ThreadPoolExecutor的各種線程池
- newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核心線程數(shù) == 最大線程數(shù)(沒有救急線程被創(chuàng)建),阻塞隊(duì)列是無界的,可以放任意數(shù)量的任務(wù),適用于任務(wù)量已知,相對(duì)耗時(shí)的任務(wù)。
- newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心線程數(shù)是0, 最大線程數(shù)是 Integer.MAX_VALUE,救急線程的空閑生存時(shí)間是 60s,意味著1. 全部都是救急線程(60s 后可以回收)2. 救急線程可以無限創(chuàng)建。
隊(duì)列采用了 SynchronousQueue 實(shí)現(xiàn)特點(diǎn)是,它沒有容量,沒有線程來取是放不進(jìn)去的(一手交錢、一手交貨)。但是在不超過最大線程數(shù)的情況下,每次都會(huì)新建新的線程。
這種對(duì)比newFixedThreadPool就是另外一種極端,沒有固定線程,每次需要多少就創(chuàng)建多少,不需要有一定容量的隊(duì)列來存儲(chǔ)任務(wù)。
- newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用場(chǎng)景:希望多個(gè)任務(wù)排隊(duì)執(zhí)行。線程數(shù)固定為 1,任務(wù)數(shù)多于 1 時(shí),會(huì)放入無界隊(duì)列排隊(duì)。任務(wù)執(zhí)行完畢,這唯一的線程也不會(huì)被釋放。
區(qū)別:
自己創(chuàng)建一個(gè)單線程串行執(zhí)行任務(wù),如果任務(wù)執(zhí)行失敗而終止那么沒有任何補(bǔ)救措施,而線程池還會(huì)新建一個(gè)線程,保證池的正常工作
Executors.newSingleThreadExecutor() 線程個(gè)數(shù)始終為1,不能修改
FinalizableDelegatedExecutorService 應(yīng)用的是裝飾器模式,只對(duì)外暴露了 ExecutorService 接口,因此不能調(diào)用 ThreadPoolExecutor 中特有的方法。Executors.newFixedThreadPool(1) 初始時(shí)為1,以后還可以修改對(duì)外暴露的是 ThreadPoolExecutor 對(duì)象,可以強(qiáng)轉(zhuǎn)后調(diào)用 setCorePoolSize 等方法進(jìn)行修改。
- invoke和execute的區(qū)別
// 執(zhí)行任務(wù)
void execute(Runnable command);
// 提交任務(wù) task,用返回值 Future 獲得任務(wù)執(zhí)行結(jié)果
<T> Future<T> submit(Callable<T> task);
創(chuàng)建線程數(shù)量多少
簡(jiǎn)單來說,CPU密集型要?jiǎng)?chuàng)建cpu 核數(shù) + 1個(gè)線程差不多,IO密集型要?jiǎng)?chuàng)建多一點(diǎn),因?yàn)镮O密集型的線程經(jīng)常在IO,CPU占有率并不高,多一點(diǎn)CPU占有率才高。線程池任務(wù)異常
- 主動(dòng)捕獲
- Future
- SynchronousQueue VS AbstractQueuedSynchronizer(AQS)
SynchronousQueue 同步隊(duì)列,放進(jìn)去的時(shí)候如果沒有人來取會(huì)進(jìn)行阻塞,如果放進(jìn)去已經(jīng)有人來取了,那就不會(huì)阻塞。
AbstractQueuedSynchronizer 簡(jiǎn)單說就是同步工具的隊(duì)列
-
任務(wù)放棄策略
-
讀寫鎖的示例
-
synchronize和aqs的區(qū)別
-
CountDownLatch
- join跟CountdownLatch的區(qū)別
- join比較底層,CountdownLatch比較高層
- join必須等到線程結(jié)束的時(shí)候才可以,而CountdownLatch只需調(diào)用。
CopyOnWriteArrayList
CopyOnWriteArrayList 可以實(shí)現(xiàn)讀寫并發(fā),但是具有弱一致性,其他的并發(fā)容器一般只做到讀讀并發(fā)。并發(fā)高和一致性是矛盾的。-
線程安全類合集
圖片.png























