線程,程序、進(jìn)程的基本概念
線程與進(jìn)程相似,但線程是一個比進(jìn)程更小的執(zhí)行單位。一個進(jìn)程在其執(zhí)行的過程中可以產(chǎn)生多個線程。與進(jìn)程不同的是同類的多個線程共享同一塊內(nèi)存空間和一組系統(tǒng)資源,所以系統(tǒng)在產(chǎn)生一個線程,或是在各個線程之間作切換工作時,負(fù)擔(dān)要比進(jìn)程小得多,也正因為如此,線程也被稱為輕量級進(jìn)程。
程序是含有指令和數(shù)據(jù)的文件,被存儲在磁盤或其他的數(shù)據(jù)存儲設(shè)備中,也就是說程序是靜態(tài)的代碼。
進(jìn)程是程序的一次執(zhí)行過程,是系統(tǒng)運行程序的基本單位,因此進(jìn)程是動態(tài)的。系統(tǒng)運行一個程序即是一個進(jìn)程從創(chuàng)建,運行到消亡的過程。簡單來說,一個進(jìn)程就是一個執(zhí)行中的程序,它在計算機中一個指令接著一個指令地執(zhí)行著,同時,每個進(jìn)程還占有某些系統(tǒng)資源如 CPU 時間,內(nèi)存空間,文件,文件,輸入輸出設(shè)備的使用權(quán)等等。換句話說,當(dāng)程序在執(zhí)行時,將會被操作系統(tǒng)載入內(nèi)存中。 線程是進(jìn)程劃分成的更小的運行單位。
線程和進(jìn)程大的不同在于基本上各進(jìn)程是獨立的,而各線程則不一定,因為同一進(jìn)程中的線程極有可能會相互影響。從另一角度來說,進(jìn)程屬于操作系統(tǒng)的范疇,主要是同一段時間內(nèi),可以同時執(zhí)行一個以上的程序,而線程則是在同一程序內(nèi)幾乎同時執(zhí)行一個以上的程序段。
線程有哪些狀態(tài)
形成死鎖的四個必要條件
說線程安全問題:什么是線程安全,如何實現(xiàn)線程安全
線程安全 - 如果線程執(zhí)行過程中不會產(chǎn)生共享資源的沖突,則線程安全。
實現(xiàn)線程安全的三種方式:
JUC中提供了幾個 Automic 類以及每個類上的原子操作就是樂觀鎖機制。不激烈情況下,性能比synchronized略遜,而激烈的時候,也能維持常態(tài)。激烈的時候,Atomic 的性能會優(yōu)于 ReentrantLock 一倍左右。但是其有一個缺點,就是只能同步一個值,一段代碼中只能出現(xiàn)一個 Atomic 的變量,多于一個同步無效。因為他不能在多個 Atomic 之間同步。
非阻塞鎖是不可重入的,否則會造成死鎖。
實現(xiàn)多線程的方法可重入代碼 使用Threadlocal 類來包裝共享變量 或者 volatile 關(guān)鍵字修飾共享變量,做到每個線程有自己的copy 線程本地存儲
繼承 Thread 類
實現(xiàn) Runnabel 接口
實現(xiàn) Callable 接口
線程同步和死鎖手寫一段死鎖的代碼,注意static關(guān)鍵字的用法,保證資源的唯一
代碼參考我的另一篇博客
線程同步 和 線程通信 是兩個概念,并且 synchronized 只能用于線程同步,不能用于線程通信(體現(xiàn)在代碼中的現(xiàn)象是,雖然線程是同步執(zhí)行的,但是會出現(xiàn)其中一個線程重復(fù)拿到時間片的現(xiàn)象)。線程通信 可以結(jié)合 synchronized + 信號燈法使用。
面試題: 為什么線程通信方法wait(),notify(),notifyAll()要被定義到Object類中?
Java中任何對象都可以被當(dāng)作鎖對象,調(diào)用wait方法,那么線程便會處于該對象的等待池中,調(diào)用notify(),notifyAll()方法,用于喚醒線程去獲取對象的鎖。Java中沒有提供任何對象使用的鎖,但是任何對象都繼承于Object類,所以定義在Object類中最合適。
線程池線程池的優(yōu)點
線程池的創(chuàng)建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueueworkQueue,
RejectedExecutionHandler handler)
corePoolSize: 線程池核心線程數(shù)量
maximumPoolSize: 線程池大線程數(shù)量
核心線程數(shù)和大線程數(shù)動畫理解
keepAliverTime: 當(dāng)活躍線程數(shù)大于核心線程數(shù)時,空閑的多余線程大存活時間
unit: 存活時間的單位
workQueue: 存放任務(wù)的隊列
handler: 超出線程范圍和隊列容量的任務(wù)的處理程序
線程池的實現(xiàn)原理
提交一個任務(wù)到線程池中,線程池的處理流程如下:
線程池的源碼解讀:
從結(jié)果可以觀察出:
線程池中Callable異常處理分析
見參考
原子性
定義: 即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。Java中的原子性操作包括:
(1)基本類型的讀取和賦值操作,且賦值必須是值賦給變量,變量之間的相互賦值不是原子性操作。
(2)所有引用reference的賦值操作
(3)java.concurrent.Atomic.* 包中所有類的一切操作
可見性
定義: 指當(dāng)多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
在多線程環(huán)境下,一個線程對共享變量的操作對其他線程是不可見的。Java提供了volatile來保證可見性,當(dāng)一個變量被volatile修飾后,表示著線程本地內(nèi)存無效,當(dāng)一個線程修改共享變量后他會立即被更新到主內(nèi)存中,其他線程讀取共享變量時,會直接從主內(nèi)存中讀取。
當(dāng)然,synchronize和Lock都可以保證可見性。synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當(dāng)中。因此可以保證可見性。
有序性
定義: 即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
Java內(nèi)存模型中的有序性可以總結(jié)為:如果在本線程內(nèi)觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。前半句是指 “線程內(nèi)表現(xiàn)為串行語義”,后半句是指"指令重排序"現(xiàn)象和"工作內(nèi)存主主內(nèi)存同步延遲"現(xiàn)象。
在Java內(nèi)存模型中,為了效率是允許編譯器和處理器對指令進(jìn)行重排序,當(dāng)然重排序不會影響單線程的運行結(jié)果,但是對多線程會有影響。Java提供volatile來保證一定的有序性。
補充:指令重排
見轉(zhuǎn)載
重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進(jìn)行排序的一種手段。重排序需要遵守一定規(guī)則:
比如:a=1; b=a; 這個指令序列,由于第二個操作依賴于第一個操作,所以在編譯時和處理器運行時不會被重排序這兩個操作。
比如:a=1; b=2; c=a+b; 這三個操作,第一步 a=1; 和第二步 b=2; 由于不存在數(shù)據(jù)依賴關(guān)系, 所以可能會發(fā)生重排序,但是 c=a+b 這個操作是不會被重排序的,因為需要保證最終的結(jié)果一定是 c=a+b=3
重排序在單線程下一定能保證結(jié)果的正確性,但是在多線程環(huán)境下,可能發(fā)生重排序,影響結(jié)果。
下例中的 1 和 2 由于不存在數(shù)據(jù)依賴關(guān)系,則有可能會被重排序,先執(zhí)行status=true再執(zhí)行a=2。而此時線程B會順利到達(dá)4處,而線程A中 a=2 這個操作還未被執(zhí)行,所以 b=a+1 的結(jié)果也有可能依然等于2。
public class TestVolatile {int a = 1;
boolean status = false;//狀態(tài)切換為true
public void changeStatus {a = 2; //1
status = true; //2
}
//若狀態(tài)為true,則為running
public void run() {if(status) { //3
int b = a + 1; //4
System.out.println(b);
}
}
}
volatile、ThreadLocal的使用場景和原理
volatile 原理volatile關(guān)鍵字最全總結(jié)
(1)volatile 變量進(jìn)行寫操作時,JVM 會向處理器發(fā)送一條 Lock 前綴的指令,將這個變量所在緩存行的數(shù)據(jù)寫會到系統(tǒng)內(nèi)存。
Lock 前綴指令實際上相當(dāng)于一個內(nèi)存屏障(也成內(nèi)存柵欄),它確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置,也不會把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時,在它前面的操作已經(jīng)全部完成。
(2)它會強制將對緩存的修改操作立即寫入主存;
(3)如果是寫操作,它會導(dǎo)致其他CPU中對應(yīng)的緩存行無效
volatile 保證可見性、有序性,不保證原子性, 所以 volatile 不適合復(fù)合操作
例如,inc++ 不是一個原子性操作,可以由讀取、加、賦值3步組成,所以結(jié)果并不能達(dá)到30000。
分析:
開啟10個線程,每個線程都自加1000次,如果不出現(xiàn)線程安全的問題最終的結(jié)果應(yīng)該就是:10*1000= 10000;可是運行多次都是小于10000的結(jié)果,問題在于 volatile 并不能保證原子性,在前面說過 counter++ 這并不是一個原子操作,包含了三個步驟:1.讀取變量inc的值;2.對inc加一;3.將新值賦值給變量inc。如果線程 A 讀取 inc 到工作內(nèi)存后,其他線程對這個值已經(jīng)做了自增操作后,那么線程A的這個值自然而然就是一個過期的值,因此,總結(jié)果必然會是小于100000的。
如果讓volatile保證原子性,必須符合以下兩條規(guī)則:
解決方法:
volatile的適用場景:
ThreadLocal 是用來維護(hù)本線程的變量的,并不能解決共享變量的并發(fā)問題。
ThreadLocal 是各線程將值存入該線程的map中,以 ThreadLocal 自身作為key,需要用時獲得的是該線程之前存入的值。如果存入的是共享變量,那取出的也是共享變量,并發(fā)問題還是存在的。
ThreadLocal的適用場景:
參考三太子敖丙——ThreadLocal
Thread 類中維護(hù)了成員變量 threadLocals,類型是ThreadLocalMap。而 ThreadLocalMap 又是 ThreadLocal 類中的 內(nèi)部類。
每一個線程維護(hù)自己的 threadLocals 成員變量,及 ThreadLocalMap 類型的 map 集合用來存放 ThreadLocal>類型的對象。
ThreadLocal 涉及到的兩個層面的內(nèi)存自動回收
1)在 ThreadLocal 層面的內(nèi)存回收
當(dāng)線程死亡時,那么所有的保存在的線程局部變量就會被回收,其實這里是指線程Thread對象中的 ThreadLocal.ThreadLocalMap threadLocals 會被回收,這是顯然的。
2)ThreadLocalMap 層面的內(nèi)存回收
如果線程可以活很長的時間,并且該線程保存的線程局部變量有很多(也就是 Entry 對象很多),那么就涉及到在線程的生命期內(nèi)如何回收 ThreadLocalMap 的內(nèi)存了,不然的話,Entry對象越多,那么ThreadLocalMap 就會越來越大,占用的內(nèi)存就會越來越多,所以對于已經(jīng)不需要了的線程局部變量,就應(yīng)該清理掉其對應(yīng)的Entry對象。
使用的方式是,Entry對象的 key 是WeakReference 的包裝,當(dāng)ThreadLocalMap 的 private Entry[] table ,已經(jīng)被占用達(dá)到了三分之二時 threshold = 2/3 (也就是線程擁有的局部變量超過了10個) ,就會嘗試回收 Entry 對象
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
ThreadLocal 源碼總結(jié)cleanSomeSlots 就是進(jìn)行回收內(nèi)存:
通過源代碼可以看到每個線程都可以獨立修改屬于自己的副本而不會互相影響,從而隔離了線程和線程。避免了線程訪問實例變量發(fā)生安全問題。同時我們也能得出下面的結(jié)論:
(1)ThreadLocal 只是操作 Thread 中的 ThreadLocalMap 對象的集合;
(2)ThreadLocalMap 變量屬于線程的內(nèi)部屬性,不同的線程擁有完全不同的 ThreadLocalMap 變量;
(3)線程中的ThreadLocalMap變量的值是在ThreadLocal對象進(jìn)行set或者get操作時創(chuàng)建的;
(4)使用當(dāng)前線程的ThreadLocalMap的關(guān)鍵在于使用當(dāng)前的ThreadLocal的實例作為key來存儲value值;
(5) ThreadLocal模式至少從兩個方面完成了數(shù)據(jù)訪問隔離,即縱向隔離(線程與線程之間的 ThreadLocalMap不同)和橫向隔離(不同的ThreadLocal實例之間的互相隔離);
(6)一個線程中的所有的局部變量其實存儲在該線程自己的同一個map屬性中;
(7)線程死亡時,線程局部變量會自動回收內(nèi)存;
(8)線程局部變量時通過一個 Entry 保存在map中,該Entry 的key是一個 WeakReference包裝的ThreadLocal, value為線程局部變量,key 到 value 的映射是通過:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 來完成的;
(9)當(dāng)線程擁有的局部變量超過了容量的2/3(沒有擴大容量時是10個),會涉及到ThreadLocalMap中Entry的回收
對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
synchronized面試題
補充:volatile、ThreadLocal、synchronized等3個關(guān)鍵字區(qū)別
見轉(zhuǎn)載
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧