并發(fā)編程基礎(chǔ)一
進(jìn)程與線(xiàn)程
進(jìn)程:是并發(fā)執(zhí)行的程序在執(zhí)行過(guò)程中分配和管理資源的基本單位,是一個(gè)動(dòng)態(tài)概念,競(jìng)爭(zhēng)計(jì)算機(jī)系統(tǒng)資源的基本單位。
線(xiàn)程:是進(jìn)程的一個(gè)執(zhí)行單元,是進(jìn)程內(nèi)可調(diào)度實(shí)體。比進(jìn)程更小的獨(dú)立運(yùn)行的基本單位。線(xiàn)程也被稱(chēng)為輕量級(jí)進(jìn)程。
一個(gè)進(jìn)程有多個(gè)線(xiàn)程,多個(gè)線(xiàn)程共享進(jìn)程的堆和方法區(qū)資源,但是每個(gè)線(xiàn)程有自己的程序計(jì)數(shù)器和棧區(qū)域。
怎么創(chuàng)建線(xiàn)程?
- 實(shí)現(xiàn) Runnable Callable 接口
--實(shí)現(xiàn) run() 方法 無(wú)返回值,無(wú)參數(shù),難以傳參;
--實(shí)現(xiàn) call 方法 有返回值,可由 FutureTask<T> 類(lèi)型變量接收結(jié)果,無(wú)參數(shù),難以傳參。結(jié)果通過(guò) FutureTask 對(duì)象的 get() 方法獲取。
- 繼承 Thread 類(lèi)
--實(shí)現(xiàn) run() 方法 無(wú)返回值,無(wú)參數(shù),由于繼承特性,便于傳參;
- 使用線(xiàn)程池技術(shù)復(fù)用線(xiàn)程
Java中與線(xiàn)程相關(guān)的方法
Object 類(lèi)作為共享資源的相關(guān)實(shí)例方法
-
wait(); 當(dāng)一個(gè)共享變量的 wait() 方法被調(diào)用時(shí),該調(diào)用線(xiàn)程會(huì)被阻塞掛起,直到
? (1).其他線(xiàn)程調(diào)用的該共享變量的 notify() 或 notifyAll() 方法喚醒;
? (2).其他線(xiàn)程調(diào)用了此線(xiàn)程的 interrupt() 方法,使此線(xiàn)程拋出異常而返回;
wait(long timeout); 執(zhí)行此方法后,若該線(xiàn)程未在規(guī)定時(shí)間類(lèi)被其他線(xiàn)程喚醒,則會(huì)拋出異常返回。
wait(long timeout , int nanos); 當(dāng) 0 < nanos <999999 ,timeout 增加 1;
notify() 與 notifyAll(); 共享變量調(diào)用后,喚醒 一個(gè)/所有 被 wait 阻塞的線(xiàn)程,不放資源;
Thread 類(lèi)相關(guān)方法
獲取當(dāng)前的 Thread實(shí)例 Thread.currentThread();
join(); 等待線(xiàn)程執(zhí)行結(jié)束的方法。
static sleep(); 當(dāng)前線(xiàn)程等待時(shí)間后繼續(xù)執(zhí)行,不放 CPU;
static yield(); 當(dāng)前線(xiàn)程禮讓?zhuān)缓笠黄饏⑴c對(duì) CPU 的競(jìng)爭(zhēng);
void interrupt(); 將此線(xiàn)程標(biāo)記為被中斷狀態(tài)的線(xiàn)程
boolean isInterrupted();檢測(cè)是否被標(biāo)記為中斷狀態(tài)
static boolean interrupted();檢測(cè),且若檢測(cè)出中斷狀態(tài),將會(huì)清除中斷標(biāo)記;
死鎖
死鎖是指兩個(gè)或兩個(gè)以上的線(xiàn)程在執(zhí)行過(guò)程中,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象。
死鎖產(chǎn)生的條件:
互斥條件:指進(jìn)程對(duì)所分配到的資源進(jìn)行排它性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)進(jìn)程占用。如果此時(shí)還有其它進(jìn)程請(qǐng)求資源,則請(qǐng)求者只能等待,直至占有資源的進(jìn)程用畢釋放。
請(qǐng)求和保持條件:指進(jìn)程已經(jīng)保持至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源已被其它進(jìn)程占有,此時(shí)請(qǐng)求進(jìn)程阻塞,但又對(duì)自己已獲得的其它資源保持不放。
不剝奪條件:指進(jìn)程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時(shí)由自己釋放。
環(huán)路等待條件:指在發(fā)生死鎖時(shí),必然存在一個(gè)進(jìn)程——資源的環(huán)形鏈,即進(jìn)程集合{P0,P1,P2,···,Pn}中的P0正在等待一個(gè) P1 占用的資源; P1 正在等待 P2 占用的資源,……, Pn 正在等待已被 P0。
一般能夠打破的死鎖條件為 請(qǐng)求并持有條件 與 環(huán)路等待條件。
ThreadLocal 與 InheritableThreadLocal原理
當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線(xiàn)程提供獨(dú)立的變量副本,所以每一個(gè)線(xiàn)程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線(xiàn)程所對(duì)應(yīng)的副本。
//Thread 類(lèi)的成員
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
讓我們看一下ThreadLocal類(lèi)存值的源碼
//ThreadLocal類(lèi)
public void set(T value) {
//獲取當(dāng)前線(xiàn)程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//獲取線(xiàn)程類(lèi)存的threadLocals inheritableThreadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
對(duì)于某一ThreadLocal來(lái)講,他的索引值i是確定的,在不同線(xiàn)程之間訪問(wèn)時(shí)訪問(wèn)的是不同的table數(shù)組的同一位置即都為table[i],只不過(guò)這個(gè)不同線(xiàn)程之間的table是獨(dú)立的。
對(duì)于同一線(xiàn)程的不同ThreadLocal來(lái)講,這些ThreadLocal實(shí)例共享一個(gè)table數(shù)組,然后每個(gè)ThreadLocal實(shí)例在table中的索引i是不同的。
ThreadLocal 不具繼承性,InheritableThreadLocal繼承自ThreadLocal,能夠解決ThreadLocal不能訪問(wèn)父線(xiàn)程本地變量的問(wèn)題。
ThreadLocal的內(nèi)存泄露問(wèn)題
/*
ThreadLocal.ThreadLocalMap類(lèi)的Entry的鍵節(jié)點(diǎn)key是弱引用,會(huì)在垃圾回收器回收垃圾時(shí)被垃圾回收,而值是被Entry對(duì)象強(qiáng)引用,不會(huì)被回收,但不能再通過(guò)Entry鍵的key找到引用的值value。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
因?yàn)樯鲜龅脑?,在ThreadLocal這個(gè)類(lèi)的get()、set()、remove()方法,均有實(shí)現(xiàn)回收 key 為 null 的 Entry 的 value所占的內(nèi)存。所以,為了防止內(nèi)存泄露(沒(méi)法訪問(wèn)到的內(nèi)存),在不會(huì)再用ThreadLocal的線(xiàn)程任務(wù)末尾,調(diào)用一次 上述三個(gè)方法的其中一個(gè)即可。
例如:
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//循環(huán)可能是因?yàn)榻鉀Qhash沖突方法為開(kāi)放地址
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();//清除引用
//歷遍過(guò)程key為null的引用,value都會(huì)被賦值為null,釋放空間
expungeStaleEntry(i);
return;
}
}
}
方法
- void set(T value);設(shè)置當(dāng)前線(xiàn)程的線(xiàn)程局部變量的值。
- public T get();該方法返回當(dāng)前線(xiàn)程所對(duì)應(yīng)的線(xiàn)程局部變量。
- public void remove();將當(dāng)前線(xiàn)程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當(dāng)線(xiàn)程結(jié)束后,對(duì)應(yīng)該線(xiàn)程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線(xiàn)程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。
- protected T initialValue();返回該線(xiàn)程局部變量的初始值,該方法是一個(gè)protected的方法,顯然是為了讓子類(lèi)覆蓋而設(shè)計(jì)的。這個(gè)方法是一個(gè)延遲調(diào)用方法,在線(xiàn)程第1次調(diào)用get()或set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null。
ThreadLocalRandom
Random 類(lèi)的種子變量為支持 CAS 操作的變量,新的隨機(jī)數(shù)生成需要兩個(gè)步驟:
首先根據(jù)老種子生成新種子
然后根據(jù)新的種子生成新的隨機(jī)數(shù)
在多線(xiàn)程環(huán)境下,由于多個(gè)線(xiàn)程同時(shí)競(jìng)爭(zhēng)原子變量的更新操作,而 CAS 操作只能同時(shí)成功一個(gè),這會(huì)導(dǎo)致大量線(xiàn)程不斷進(jìn)行自旋重試,這會(huì)降低并發(fā)性。
與ThreadLocal原理相同,將種子存于線(xiàn)程中,每個(gè)線(xiàn)程有自己的種子,從而做到線(xiàn)程安全。
ThreadLocalRandom 類(lèi)通過(guò) ThreadLocalRandom.current() 來(lái)獲取當(dāng)前線(xiàn)程的隨機(jī)數(shù)生成器,此類(lèi)為工具類(lèi),繼承了 Random 類(lèi)但沒(méi)有使用其種子變量,而是調(diào)用 Thread 類(lèi)中實(shí)例的 threadLocalRandomSeed 變量。
通過(guò)讓每一個(gè)線(xiàn)程復(fù)制一份變量,使得每個(gè)線(xiàn)程對(duì)變量的操作是在本地副本上進(jìn)行,從而避免競(jìng)爭(zhēng),提高性能。
其他
用戶(hù)線(xiàn)程與守護(hù)線(xiàn)程的概念
注
參考書(shū)《Java并發(fā)編程之美》
ThreadLocal的內(nèi)存泄露問(wèn)題 參考:https://blog.csdn.net/yanluandai1985/article/details/82590336