1.死鎖
線程同步的時候會對對象監視器所監視的操作上鎖,也就是隻有當前線程能夠進來,其他線程是進不來的,除非你拿到了監視器。
死鎖:當線程A進入到了X對象的監視器內,線程B進入到了Y對象的監視器內,X對象的監視器內部調用了Y對象監視器內部的操作,所以線程A想正常終結的話,必須等線程B交出監視器(終結或掛起(掛起不考慮)),然後以可重入鎖的方式進入Y對象監視器的內部(可重入鎖的概念,後續博文會解釋),執行完相關操作,然後終結;而巧的是Y對象的監視器調用了X對象監視器內部的操作,B線程若是想正常終結的話,必須等A線程交出監視器,這樣互相等待的狀態稱爲死鎖。
死鎖案例:
public class DeadLockTest {
public static void main(String[] args) {
/*
* 所謂死鎖,就是線程A進入到X對象的監視器內部,線程B進入到了Y對象的監視器內部,而A在X監視器內部需要調用B的監視器內部操作才能結束,所以就調用了,然而,得等B交出監視器(B終結或者掛起(掛起除外))
* 而巧的是B要想終結的話,得執行A監視器內部的操作才能執行,這樣就造成了互相等待,而且不會有解,除非有一個能夠不以終結的方式交出監視器
*/
Task1 t1 = new Task1();
Task2 t2 = new Task2();
new Thread(()->{
t1.task1(t2);
}).start();
new Thread(()->{
t2.task1(t1);
}).start();
// 運行結果:
// Task1...task1
// Task2...task1
// 正在運行..(必須強行終止否則沒有頭啊)
}
}
class Task1{
public synchronized void task1(Task2 task2){
System.out.println("Task1...task1");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
task2.task2();
}
public synchronized void task2(){
System.out.println("Task1...task2");
}
}
class Task2{
public synchronized void task1(Task1 task1){
System.out.println("Task2...task1");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
task1.task2();
}
public synchronized void task2(){
System.out.println("Task2...task2");
}
}
2.線程狀態間轉化
線程總共有5個狀態:被創建後還沒有被執行狀態、執行狀態、阻塞狀態(等待,不交出鎖)、等待狀態、終結態
相互之間的轉化圖如下:
調用sleep方法後處於等待狀態,但是不交出監視器持有;調用wait方法後也是處於等待狀態,但是交出監視器持有。
3.線程組
線程組爲管理一組線程提供了一種便利的方式,可以把一組線程當成一個單位進行管理。對於希望對一組線程進行操作(掛起、恢復等)是很便利的。
線程組類:ThreadGroup(String groupName) ThreadGroup(ThreadGroup parentGp,String groupName)
對於如何設置線程所屬的線程組,以及如何獲取線程組中的線程由下文的代碼實例說明。
對於線程組的理解,可以從Java中線程的組織關係下手:
1.Java中線程是以樹的結構進行管理的,樹的根是主線程,由主線程創建的線程是根的孩子,由此遞歸
2.這顆樹上的線程都是活着的線程:沒有終結也沒有掛起
3.線程組在這個樹中是什麼呢?線程組是非葉子節點。如果沒有加入線程組的話,所有的子線程都是根(主線程)的孩子,加了線程組,那所有的線程(除主線程)都是葉子節點,從樹的不同非葉子節點(線程組)總是能獲取這個節點的所有子孫節點(子線程),說明代碼如下:
實例代碼如下:
public class ThreadGroupTest {
public static void main(String[] args) {
/*
* 對於任何一個程序,首先有一個主線程,這個主線程是程序主函數(入口函數)啓動的時候啓動的
* 由主線程M開的線程A之間的關係是:M和A位於同一個線程組(即使在開新線程的時候不指明線程組的話),M是A的父線程
* 同樣,如果A線程再開線程,那開的線程就是A線程的子線程
* 可用的三個構造器分別爲:
* Thread(ThreadGroup groupOb,Runnable threadOb):指明線程組和Runnable實現
* Thread(ThreadGroup groupOb,Runnable threadOb,String threadName):指明線程租和Runnable實現和線程名稱
* Thread(ThreadGroup groupOb,String threadName):指明線程組和線程名稱,通常在Thread子類的構造器中調用super方法時使用這個
* 引入線程組的目的是爲了批管理線程
*/
//t1和主線程位於同一線程組
Thread t1 = new Thread(()->{
//t1_1和主線程位於同一線程組
Thread t1_1 = new Thread(()->{
//加上死循環是爲了保證這個線程一直在這個線程組中活下去,已經終結的線程是不能通過線程組獲取的
while(true);
});
t1_1.start();
while(true);
});
t1.start();
//t2屬於線程組X
Thread t2 = new Thread(new ThreadGroup("X"),()->{
Thread t2_2 = new Thread(new ThreadGroup("Y"),()->{
while(true);
});
t2_2.start();
while(true);
});
t2.start();
//獲取這兩個線程租
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
//獲取展示這兩個線程組中都有哪些線程
tg1.list();
tg2.list();
//分別獲取到每個線程組中的每個線程
Thread[] threadarray1 = new Thread[tg1.activeCount()];
Thread[] threadarray2 = new Thread[tg2.activeCount()];
tg1.enumerate(threadarray1);
tg2.enumerate(threadarray2);
//操作已經get到的線程組中的線程,這裏僅僅是打印出來,實際就是這樣進行批處理的哦
System.out.println("Threads in the main Thread Group");
for(Thread t:threadarray1){
System.out.println(t);
}
System.out.println("Threads in the X Thread Group");
for(Thread t:threadarray2){
System.out.println(t);
}
// 運行結果(帶#表示不是運行結果,是後加的註釋):
// #樹結構:
// java.lang.ThreadGroup[name=main,maxpri=10] #從根節點獲取子線程
// Thread[main,5,main]
// Thread[Thread-0,5,main]
// java.lang.ThreadGroup[name=X,maxpri=10]
// Thread[Thread-1,5,X]
// java.lang.ThreadGroup[name=Y,maxpri=10]
// Thread[Thread-3,5,Y]
// java.lang.ThreadGroup[name=X,maxpri=10] #從X線程組獲取子線程
// Thread[Thread-1,5,X]
// java.lang.ThreadGroup[name=Y,maxpri=10]
// Thread[Thread-3,5,Y]
// #數組表示:
// Threads in the main Thread Group
// Thread[main,5,main]
// Thread[Thread-0,5,main]
// Thread[Thread-2,5,main]
// Thread[Thread-1,5,X]
// Thread[Thread-3,5,Y]
// Threads in the X Thread Group
// Thread[Thread-1,5,X]
// Thread[Thread-3,5,Y]
}
}