進程與線程的區(qū)別
- 進程:是系統(tǒng)進行分配和管理資源的基本單位
- 線程:進程的一個執(zhí)行單元,是進程內(nèi)調(diào)度的實體、是CPU調(diào)度和分派的基本單位,是比進程更小的獨立運行的基本單位。線程也被稱為輕量級進程,線程是程序執(zhí)行的最小單位。
- 一個程序至少一個進程,一個進程至少一個線程。
- 進程有自己的獨立地址空間,每啟動一個進程,系統(tǒng)就會為它分配地址空間,建立數(shù)據(jù)表來維護代碼段、堆棧段和數(shù)據(jù)段,這種操作非常昂貴。 而線程是共享進程中的數(shù)據(jù)的,使用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創(chuàng)建一個線程的開銷也比進程要小很多。 線程之間的通信更方便,同一進程下的線程共享全局變量、靜態(tài)變量等數(shù)據(jù),而進程之間的通信需要以通信的方式進行。 如何處理好同步與互斥是編寫多線程程序的難點。 多進程程序更健壯,進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響, 而線程只是一個進程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,所以可能一個線程出現(xiàn)問題,進而導(dǎo)致整個程序出現(xiàn)問題
線程的狀態(tài)及其相互轉(zhuǎn)換
初始(NEW):新創(chuàng)建了一個線程對象,但還沒有調(diào)用start()方法。
運行(RUNNABLE):處于可運行狀態(tài)的線程正在JVM中執(zhí)行,但它可能正在等待來自操作系統(tǒng)的其他資源,例如處理器。
阻塞(BLOCKED):線程阻塞于synchronized鎖,等待獲取synchronized鎖的狀態(tài)。
等待(WAITING):Object.wait()、join()、 LockSupport.park(),進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)。
超時等待(TIME_WAITING):Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUntil,該狀態(tài)不同于WAITING,它可以在指定的時間內(nèi)自行返回。
終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢自己結(jié)束,或者產(chǎn)生了異常而結(jié)束。
創(chuàng)建線程的方式
- 繼承Thread,并重寫父類的run方法
- 實現(xiàn)Runable接口,并實現(xiàn)run方法
- 使用匿名內(nèi)部類
- Lambda表達式
- 線程池
什么是線程安全性
當(dāng)多個線程訪問某個類,不管運行時環(huán)境采用何種調(diào)度方式或者這些線程如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個類都能表現(xiàn)出正確的行為,那么就稱這個類為線程安全的。----《并發(fā)編程實戰(zhàn)》
- 什么是線程不安全?
多線程并發(fā)訪問時,得不到正確的結(jié)果。
原子性操作
一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。
volatile關(guān)鍵字僅僅保證可見性,并不保證原子性 ? synchronize關(guān)機字,使得操作具有原子性。
synchronized關(guān)鍵字
-
內(nèi)置鎖
每個java對象都可以用做一個實現(xiàn)同步的鎖,這些鎖稱為內(nèi)置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內(nèi)置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。
-
互斥鎖
內(nèi)置鎖是一個互斥鎖,這就是意味著最多只有一個線程能夠獲得該鎖,當(dāng)線程A嘗試去獲得線程B持有的內(nèi)置鎖時,線程A必須等待或者阻塞,直到線程B釋放這個鎖,如果B線程不釋放這個鎖,那么A線程將永遠等待下去。
- 修飾普通方法:鎖住對象的實例
- 修飾代碼塊: 鎖住一個對象 synchronized (lock) 即synchronized后面括號里的內(nèi)容
- 修飾Class: 鎖住整個類
- 修飾靜態(tài)方法:鎖住整個類
當(dāng)作用于靜態(tài)方法時,鎖住的是Class實例,又因為Class的相關(guān)數(shù)據(jù)存儲在永久帶PermGen(jdk1.8則是metaspace),永久帶是全局共享的,因此靜態(tài)方法鎖相當(dāng)于類的一個全局鎖,會鎖所有調(diào)用該方法的線程;
//放在方法 鎖得是對象 和 synchronized(this)是一樣的
public synchronized void lockMethod(){
System.out.println(Thread.currentThread().getName()+" lockMethod");
}
public void lockMethod_CodeBlockByThis() {
//代碼塊 this鎖的是對象
synchronized(this) {
System.out.println(Thread.currentThread().getName()+" lockMethod_CodeBlockByThis");
}
}
//類鎖
public void lockMethod_CodeBlockByClass() {
//代碼塊 Class鎖的是類,無論任何地方,任何類只要是鎖的是同一個class,并發(fā)時都會等待
synchronized(ThreadDemo.class) {
System.out.println(Thread.currentThread().getName()+"lockMethod_CodeBlockByClass");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//放在靜態(tài)方法上,鎖的是當(dāng)前的class,此處的鎖和synchronized(ClassA.class)是同樣的
public synchronized static void lockStaticMethod() {
System.out.println(Thread.currentThread().getName()+" lockStaticMethod");
}
volatile關(guān)鍵字及其使用場景
指令重排:CPU和編譯器為了提升程序執(zhí)行的效率,會按照一定的規(guī)則允許進行指令優(yōu)化,在某些情況下,這種優(yōu)化會帶來一些執(zhí)行的邏輯問題,主要的原因是代碼邏輯之間是存在一定的先后順序,在并發(fā)執(zhí)行情況下,會發(fā)生二義性,即按照不同的執(zhí)行邏輯,會得到不同的結(jié)果信息。
- 能且僅能修飾變量
- 保證該變量的可見性,volatile關(guān)鍵字僅僅保證可見性,并不保證原子性
- 禁止指令重排序
- A、B兩個線程同時讀取volatile關(guān)鍵字修飾的對象,A讀取之后,修改了變量的值,修改后的值,對B線程來說,是可見
- 使用場景 1:作為線程開關(guān) 2:單例,修飾對象實例,禁止指令重排序
單例與線程安全
- 餓漢式--本身線程安全
在類加載的時候,就已經(jīng)進行實例化,無論之后用不用到。如果該類比較占內(nèi)存,之后又沒用到,就白白浪費了資源。
public class HungerSingleton {
private static HungerSingleton ourInstance = new HungerSingleton();
public static HungerSingleton getInstance() {
return ourInstance;
}
private HungerSingleton() {
}
}
- 懶漢式 -- 最簡單的寫法是非線程安全的
在需要的時候再實例化
public class LazySingleton {
private static volatile LazySingleton ourInstance = null;
public static LazySingleton getInstance() {
if(null == ourInstance){
synchronized (ourInstance){
if(ourInstance == null){
ourInstance = new LazySingleton();
}
}
}
return ourInstance;
}
private LazySingleton() {
}
}
如何保證單例?
private私有的空參構(gòu)造器
static的對象和方法getInstance
如何避免線程安全性問題
-
線程安全性問題成因
多線程環(huán)境 多個線程操作同一共享資源 對該共享資源進行了非原子性操作-
如何避免
打破成因中三點任意一點
1:多線程環(huán)境--將多線程改單線程(必要的代碼,加鎖訪問)
2:多個線程操作同一共享資源--不共享資源(ThreadLocal、不共享、操作無狀態(tài)化、不可變)
3:對該共享資源進行了非原子性操作-- 將非原子性操作改成原子性操作(加鎖、使用JDK自帶的原子性操作
的類、JUC提供的相應(yīng)的并發(fā)工具類) -