死鎖:
(1)定義:在多線程中(兩個或以上線程),因為資源搶占而造成線程無限等待的問題。由于一個線程可以擁有多把鎖,但是一個鎖隻能被一個線程擁有。
(2)死鎖造成的四個因素(同時):
1.互斥條件(一個資源隻能被一個線程持有);
2.請求擁有條件(一個線程持有了一個資源之後,又請求另一個資源);
3.不可剝奪條件(一個資源被一個線程擁有之後,如果這個線程不釋放此資源,那麼其他線程不能強制獲得此資源);
4.環路等待條件(多個線程在擷取資源時形成了一個環形鍊);
(3)如何解決死鎖的問題?
從以下兩個條件入手,修改以下條件任意一個:
1.請求擁有條件
2.環路等待條件:
解決死鎖可以通過控制擷取鎖的順序來解決死鎖問題(破壞環路等待條件)。
線程等待:
sleep休眠缺陷:必須傳遞一個明确的結束時間。
線程的通訊機制:一個線程的動作可以讓另一個線程感覺到。
wait(線程休眠)/notify(線程喚醒)/nofifyAll(喚醒目前鎖對應的全部線程)
1.在使用以上方法時必須要加鎖;
wait為什麼要加鎖?
因為在進行wait操作時,會先釋放鎖;
為什麼要釋放鎖?
因為wait在不傳任何參數時,預設是永久等待,這樣會造成一個資源一直被占用。
2.加鎖對象和以上方法的加鎖對象必須保持一緻;
3.一組wait(休眠)/notify(喚醒)/nofifyAll(喚醒全部)必須加同一把鎖;
4.notifyAll隻能喚醒目前對象對應的線程;
sleep(0)和wait(0)的差別:
1.sleep是Thread的一個靜态方法;而wait是Object的方法;
2.sleep(0)立即觸發一次CPU資源的搶占,wait(0)代表永久的等待下去;
wait和sleep差別:
相同點:
都可以讓目前線程休眠;
都必須要處理一個Interrupt的異常;
不同點:
wait來自于Object中的一個方法,而sleep來自Thread類;
傳參不同,wait可以沒有參數,而sleep必須有一個大于0的參數;
wait必須要加鎖,sleep使用時不用加鎖;
wait使用時會釋放鎖,sleep沒有鎖;
wait預設不傳參的情況下,會進入WAITING狀态,sleep會進入TIMEDWAITING狀态;
為什麼wait會放到Object中而不是Thread?
wait必須要加鎖和釋放鎖,而鎖屬于對象級别,而非線程級别(一個線程線程擁有多把鎖),為了靈活起見(釋放哪把鎖),就把wait放在Object中。
LockSupport.park()方法——新版本休眠——WAITING——不用加鎖,不用處理異常
LockSupport.unpark(線程)方法——新版本喚醒休眠——可以控制線程喚醒順序,不能和notify等混用
LockSupport.parkUntil(休眠時間)與wait差別:
相同點:
1.都可以線程休眠;
2.都可以傳參不傳參,并且線程狀态一緻;
不同點:
1.wait必須要配合synchronized一起使用(加鎖),而LockSupport不用;
2.wait隻能喚醒全部和随機的一個線程,而LockSupport可以喚醒指定線程;
線程池:
1.線程的建立需要開辟記憶體資源:本地方法棧、Java虛拟機棧、程式計數器等線程私有變量的記憶體,頻繁的建立和銷毀會造成性能問題;
2.使用線程不能很好的管理任務和友好的拒絕任務;
定義:使用池化技術來管理和使用線程的技術就叫做線程池。
線程池的建立方式總共包含七種:
方式一:建立固定個數的線程池;Executors——當對目前代碼執行很了解,知道執行多少個任務
public static void main(String[] args) {
//建立固定個數的線程池
ExecutorService executorService=Executors.newFixedThreadPool(5);
//執行一個任務
/* executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("線程名"+Thread.currentThread().getName());
}
});*/
//執行多個任務
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("線程名"+Thread.currentThread().getName());
}
});
}
}
建立了10個線程的線程池來執行兩個任務,實際建立了幾個線程?答案:2個
線程池執行流程:當拿到一個任務之後,會判斷目前線程池裡面的線程數量是否達到最大值,如果沒有就建立新的線程執行任務;當任務來了之後,線程池的線程數量已經是最大值,并且沒有空閑線程,那麼會将任務放到任務隊列中等待執行;
線程池裡面的兩個重要任務:
1.線程;
2.任務隊列;
方式二:建立帶緩存的線程池;——當有大量短期任務的時候适用,根據任務的數量來建立對應的線程數;
public static void main(String[] args) {
//建立帶緩存的線程池
ExecutorService service=Executors.newCachedThreadPool();
for(int i=0;i<10;++i){
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
方式三:建立可執行定時任務的線程池;
public static void main(String[] args) {
//建立可執行定時任務的線程池
ScheduledExecutorService service=Executors.newScheduledThreadPool(10);
//執行任務
System.out.println(new Date());
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(new Date());
}
},1,3, TimeUnit.SECONDS);
System.out.println(new Date());
}
參數:線程執行的任務、定時任務延遲多長時間開始執行、定時任務的執行頻率、配合參數2和參數3的時間機關。
schedule和scheduleWithFixedDelay的差別:
1.前者沒有延遲執行的時間設定;
2.前者隻執行一次;
scheduleAtFixedRate:開始時間是以上次任務的開始時間去記錄的,固定的頻率,穩定;
scheduleWithFixedDelay:任務的開始時間是以上次任務的結束時間去記錄的,适合用于任務不穩定的,不穩定;
方式四:建立單個執行定時任務的線程池;
單個線程的線程池有什麼意義?
1.無需頻繁的建立和銷毀線程;
2.可以更好的配置設定和管理以及存儲任務(任務隊列);
方式五:單個線程的線程池;
public static void main(String[] args) {
//建立單個線程的線程池
ExecutorService service=Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
for(int i=0;i<10;++i){
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
方式六:(jdk8+)根據目前的工作環境(CPU核心數、任務量),異步線程池
public static void main(String[] args) {
//根據目前工作環境來建立
ExecutorService service=Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
while(!service.isTerminated()){}//任務執行狀态
}
同步與異步:
同步:按照某種規則按序執行的就叫同步(解決線程不安全的主要手段);
流程:main調用線程池-》線程池執行完之後關閉線程池-》main也會随之關閉;
異步:main調用異步線程池-》異步線程池背景執行,對于main線程來說,異步線程池已經執行完成,main會直接關閉線程;
Executors建立線程池的問題:
1.線程數量不可控;(線程的過度切換和搶占,造成執行速度會變慢)
2.任務數量不可控,當任務量比較大的時候會造成記憶體溢出異常(OOM)
方式七:原始建立方式:ThreadPoolExecutors
public static void main(String[] args) {
//原始的建立線程池的方法
ThreadPoolExecutor executor=new ThreadPoolExecutor(
5,//核心線程數
10,//最大線程數,大于等于核心線程數
60,//臨時線程的最大生命周期long類型
TimeUnit.SECONDS,//時間機關
new LinkedBlockingDeque<>(1000)//任務隊列
);
/*for (int i = 0; i < 20; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}*/
ThreadFactory threadFactory=new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread=new Thread(r);
thread.setName("線程666");
return thread;
}
};
ThreadPoolExecutor executor1=new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
threadFactory
);
executor1.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
當任務數量高于核心線程數,并且任務隊列未放滿,則會先将多出來的任務放在任務隊列中;
當任務數量高于核心線程數,并且任務隊列放滿了,那麼此時判斷最大線程數是否大于目前線程池中執行的線程數,大于的話,就開辟臨時線程,否則拒絕;
線程池建立線程是懶加載,在有任務的時候才建立對應任務數的線程去執行任務;
拒絕政策:JDK4種拒絕政策+自定義拒絕政策
1.預設拒絕政策:
2.調用調用者的線程來執行任務:
3.忽略政策;(忽略新任務)
4.忽略老任務;
5.自定義拒絕政策;
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("執行了自定義的拒絕政策");
}
}
線程池的兩種執行方式:
1.execute,執行Runnable,無傳回值;
2.submit 執行 (Runnable或者Callable);
execute如果出現記憶體溢出,則會列印異常,submit不會;
關閉線程池:
shutdown與shutdownNow
1.shutdown:拒絕新任務,等待線程池中的任務執行完之後,再停止線程池;
2.showdownNow:拒絕執行新任務,不會等待任務隊列中的任務執行完成,就關閉線程池;
線程池的狀态:(線程池狀态不等于線程狀态)
running、shutdown(調用shutdown)、stop(調用shutdownNow之後)、tidying(當pool是空的時候,銷毀的前置狀态)、terminated(銷毀狀态)
線程池的狀态隻是給開發者使用的,客戶機不可見;
ThreadLocal:線程級别的私有變量;
1.set:将私有變量放入線程中;
2.get:擷取線程中的變量;
3.remove:從線程中移除變量(不移除:髒讀、OOM);
4.initialValue:初始化ThreadLocal;
5.withInitial:初始化;
什麼情況下不會執行initialValue?為什麼?
(1)在進行了set操作之後不會執行initialValue;
(2)原因:ThreadLocal是懶加載的,當調用了get方法之後,才會嘗試執行initialValue方法,但在調用之前,會先嘗試擷取ThreadLocal的值,如果确實擷取不到,再執行初始化,執行完初始化後,這個初始化方法不會再執行;
ThreadLocal的使用場景:
1.解決線程安全問題;
2.實作線程級别的資料傳遞;
ThreadLocal的缺點:
1.不可繼承(父線程的東西,子線程通路不到);
如何解決不可繼承性?
2.髒讀:在一個線程中讀取到不屬于自己的資料;
線程使用ThreadLocal不會出現髒讀,線上程池裡使用ThreadLocal會出現髒讀,因為線程池會進行線程複用;
解決辦法:
1.避免使用靜态屬性(靜态屬性線上程池中會被複用);
2.使用remove,移除變量;
3.記憶體溢出問題:(最常出現的問題)
當一個線程執行完,不會釋放這個線程所占用的記憶體,或者記憶體釋放不及時的情況都叫做記憶體溢出;
ThreadLocal使用:
1.set:将私有變量設定到線程;
2.get:從線程中擷取私有變量;
3.remove:将線程中的私有變量移除;
4.initialValue:初始化;
5.withIitialValue:初始化;
使用場景:
1.解決線程安全問題;
2.線程級别的資料傳遞;
缺點:
1.不能實作父子線程之間的資料傳遞;
2.髒資料:ThreadLocal+線程池;
3.記憶體溢出(原因:線程池是長生命周期的,而線程是執行完任務線程就結束了(線程相關的資源都會釋放掉),但是線程池是長生命周期的,線程池中又有線程,線程裡面有ThreadLocalMap,Entry數組中存的是key和value,value是強引用,不會被釋放);
HashMap與ThreadLocalMap處理哈希沖突的差別?
前者使用連結清單法,後者使用開放尋址法;開放尋址法的特點和使用場景是資料量比較少的情況下性能更好;而HashMap裡面存儲的資料通常情況下資料量比較多,是以采用連結清單法更合适;
為什麼将ThreadLocal中的key設定為弱引用?
為了最大程度的避免OOM;
解決記憶體溢出問題:使用remove;