這兩天爲了定位JBOSS總是掛死的問題,學習了一下JAVA多線程方面的知識,在此總結一下
一、在Java程序中,JVM負責線程的調度。線程調度是指按照特定的機制爲多個線程分配CPU的使用權。
調度的模式有兩種:分時調度和搶佔式調度。分時調度是全部線程輪流得到CPU使用權,並平均分配每一個線程佔用CPU的時間;搶佔式調度是根據線程的優先級別來獲取CPU的使用權。JVM的線程調度模式採用了搶佔式模式。
二、Thread類實際上也是實現了Runnable接口的類。
在啓動的多線程的時候,須要先經過Thread類的構造方法Thread(Runnable target) 構造出對象,而後調用Thread對象的start()方法來運行多線程代碼。
實際上全部的多線程代碼都是經過運行Thread的start()方法來運行的。所以,不論是擴展Thread類仍是實現Runnable接口來實現多線程,最終仍是經過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。
三、JAVA多線程涉及到2個問題,一個是線程的調度,另外一個是線程的同步
四、線程的狀態有:new、runnable、running、waiting、timed_waiting、blocked、dead
當執行new Thread(Runnable r)後,新建立出來的線程處於new狀態,這種線程不可能執行
當執行thread.start()後,線程處於runnable狀態,這種狀況下只要獲得CPU,就能夠開始執行了。runnable狀態的線程,會接受JVM的調度,進入running狀態,可是具體什麼時候會進入這個狀態,是隨機不可知的
running狀態中的線程最爲複雜,可能會進入runnable、waiting、timed_waiting、blocked、dead狀態:
若是CPU調度給了別的線程,或者執行了Thread.yield()方法,則進入runnable狀態,可是也有可能馬上又進入running狀態
若是執行了Thread.sleep(long),或者thread.join(long),或者在鎖對象上調用object.wait(long)方法,則會進入timed_waiting狀態
若是執行了thread.join(),或者在鎖對象上調用了object.wait()方法,則會進入waiting狀態
若是進入了同步方法或者同步代碼塊,沒有獲取鎖對象的話,則會進入blocked狀態
處於waiting狀態中的線程,若是是由於thread.join()方法進入等待的話,在目標thread執行完畢以後,會回到runnable狀態;若是是由於object.wait()方法進入等待的話,在鎖對象執行object.notify()或者object.notifyAll()以後會回到runnable狀態
處於timed_waiting狀態中的線程,和waiting狀態中的差很少,只不過是設定時間到了,就會回到runnable狀態
處於blocked狀態中的線程,只有獲取了鎖以後,纔會脫離阻塞狀態
當線程執行完畢,或者拋出了未捕獲的異常以後,會進入dead狀態,該線程結束
五、當線程池中線程都具備相同的優先級,調度程序的JVM實現自由選擇它喜歡的線程。這時候調度程序的操做有兩種可能:一是選擇一個線程運行,直到它阻塞或者運行完成爲止。二是時間分片,爲池內的每一個線程提供均等的運行機會。
六、設置線程的優先級:線程默認的優先級是建立它的執行線程的優先級。能夠更改線程的優先級。
JVM從不會改變一個線程的優先級。然而,1-10之間的值是沒有保證的。一些JVM可能不能識別10個不一樣的值,而將這些優先級進行每兩個或多個合併,變成少於10個的優先級,則兩個或多個優先級的線程可能被映射爲一個優先級。
七、Thread.yield()方法做用是:暫停當前正在執行的線程對象,並執行其餘線程。
yield()應該作的是讓當前運行線程回到可運行狀態,以容許具備相同優先級的其餘線程得到運行機會。所以,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。可是,實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。
結論:yield()從未致使線程轉到等待/睡眠/阻塞狀態。在大多數狀況下,yield()將致使線程從運行狀態轉到可運行狀態,但有可能沒有效果。
八、另外一個問題是線程的同步,這個我感受比調度更加複雜一些
Java中每一個對象都有一個「內置鎖」,也有一個內置的「線程表」
當程序運行到非靜態的synchronized方法上時,會得到與正在執行代碼類的當前實例(this實例)有關的鎖;當運行到同步代碼塊時,得到與聲明的對象有關的鎖
釋放鎖是指持鎖線程退出了synchronized方法或代碼塊。
當程序運行到synchronized同步方法或代碼塊時對象鎖才起做用。
一個對象只有一個鎖。因此,若是一個線程得到該鎖,就沒有其餘線程能夠得到鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其餘線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
九、當提到同步(鎖定)時,應該清楚是在哪一個對象上同步(鎖定)?
十、
obj.wait()
obj.notify()
obj.notifyAll()
關於這3個方法,有一個關鍵問題是:
必須從同步環境內調用wait()、notify()、notifyAll()方法。只有擁有該對象的鎖的線程,才能調用該對象上的wait()、notify()、notifyAll()方法
與每一個對象具備鎖同樣,每一個對象也能夠有一個線程列表,他們等待來自該對象的通知。線程經過執行對象上的wait()方法得到這個等待列表。從那時候起,它再也不執行任何其餘指令,直到調用對象的notify()方法爲止。若是多個線程在同一個對象上等待,則將只選擇一個線程(不保證以何種順序)繼續執行。若是沒有線程等待,則不採起任何特殊操做。
十一、下面貼幾個代碼實例,配合jstack命令說明一下
java
public class ThreadA { 編程
public static void main(String[] args) { 多線程
ThreadB b = new ThreadB();// ThreadB status: new 學習
b.start();// ThreadB status: runnable this
synchronized (b) { spa
try { 線程
System.out.println("等待對象b完成計算。。。"); 對象
Thread.sleep(60000); 接口
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("b對象計算的總和是:" + b.total);
}
}
}
public class ThreadB extends Thread {
int total;
public void run() {
synchronized (this) {
for (int i = 0; i < 101; i++) {
total += i;
}
notifyAll();
}
}
}
jstack輸出的結果是:
"main" prio=6 tid=0x00846800 nid=0x1638 waiting on condition [0x0092f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:20)
- locked <0x22a18a90> (a net.kyfxbl.lock.ThreadB)
"Thread-0" prio=6 tid=0x02bbb800 nid=0x1410 waiting for monitor entry [0x02f0f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:11)
- waiting to lock <0x22a18a90> (a net.kyfxbl.lock.ThreadB)
能夠看到,主線程和新線程在同一個對象上鎖定,主線程的方法裏執行了Thread.sleep(60000),所以進入了TIMED_WAITING狀態,而新線程則進入BLOCKED狀態
public class ThreadA {
public static void main(String[] args) {
ThreadB b = new ThreadB();// ThreadB status: new
b.start();// ThreadB status: runnable
synchronized (b) {
try {
System.out.println("等待對象b完成計算。。。");
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("b對象計算的總和是:" + b.total);
}
}
}
public class ThreadB extends Thread {
int total;
public void run() {
synchronized (this) {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 101; i++) {
total += i;
}
notifyAll();
}
}
}
jstack輸出的結果是:
"main" prio=6 tid=0x00846800 nid=0x1684 in Object.wait() [0x0092f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x22a18b08> (a net.kyfxbl.lock.ThreadB)
at java.lang.Object.wait(Object.java:485)
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:22)
- locked <0x22a18b08> (a net.kyfxbl.lock.ThreadB)
"Thread-0" prio=6 tid=0x02bcc800 nid=0x19c waiting on condition [0x02f0f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:12)
- locked <0x22a18b08> (a net.kyfxbl.lock.ThreadB)
2個線程仍是在同一個對象上同步,但此次主線程馬上執行了b.wait()方法,所以釋放了對象b上的鎖,本身進入了WAITING狀態。接下來新線程獲得了對象b上的鎖,因此沒有進入阻塞狀態,緊接着執行Thread.sleep(60000)方法,進入了TIMED_WAITING狀態
public class ThreadA {
public static void main(String[] args) {
ThreadB b = new ThreadB();// ThreadB status: new
b.start();// ThreadB status: runnable
synchronized (b) {
try {
System.out.println("等待對象b完成計算。。。");
b.wait();// ThreadB status: running
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("b對象計算的總和是:" + b.total);
}
}
}
public class ThreadB extends Thread {
int total;
public void run() {
synchronized (this) {
for (int i = 0; i < 101; i++) {
total += i;
}
notifyAll();
try {
System.out.println("我要睡了");
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
jstack輸出的結果是:
"main" prio=6 tid=0x00846800 nid=0x3ec in Object.wait() [0x0092f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x22a18ba0> (a net.kyfxbl.lock.ThreadB)
at java.lang.Object.wait(Object.java:485)
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:20)
- locked <0x22a18ba0> (a net.kyfxbl.lock.ThreadB)
"Thread-0" prio=6 tid=0x02bbb800 nid=0x14b4 waiting on condition [0x02f0f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:19)
- locked <0x22a18ba0> (a net.kyfxbl.lock.ThreadB)
當主線程執行b.wait()以後,就進入了WAITING狀態,可是新線程執行notifyAll()以後,有一個瞬間主線程回到了RUNNABLE狀態,可是好景不長,因爲這個時候新線程尚未釋放鎖,因此主線程馬上進入了BLOCKED狀態
十二、當在對象上調用wait()方法時,執行該代碼的線程當即放棄它在對象上的鎖。然而調用notify()時,並不意味着這時線程會放棄其鎖。若是線程仍然在完成同步代碼,則線程在移出以前不會放棄鎖。所以,只要調用notify()並不意味着這時該鎖被釋放
1三、與線程休眠相似,線程的優先級仍然沒法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的機率較大,優先級低的並不是沒機會執行。
1四、在一個線程中開啓另一個新線程,則新開線程稱爲該線程的子線程,子線程初始優先級與父線程相同。
1五、JRE判斷程序是否執行結束的標準是全部的前臺執線程行完畢了,而無論後臺線程的狀態,所以,在使用後臺線程時候必定要注意這個問題。
1六、下面說說咱們此次JBOSS掛死問題的處理方法
現象:系統運行一段時間以後,發現有幾個子系統沒法訪問了,可是另外幾個能夠。CPU佔用達到100%
觀察了一下,發現沒法訪問的應用都部署在同一個JBOSS裏,因而把該JBOSS的堆棧用jstack命令輸出
發現裏面有大量的線程處於BLOCKED狀態,均是在執行到c3p0的一個方法裏的某一行時,BLOCKED住了
因而下載c3p0的源碼,跟進去看了一下,這是一個同步方法,內部會去獲取數據庫鏈接,若是獲取到鏈接,就進行下一步操做,若是獲取不到,就執行sleep(long timeout)方法。
反推一下,我猜想多是這樣的:
因爲某段代碼沒有釋放數據庫鏈接-->鏈接池中的鏈接耗盡-->部分線程無限TIMED_WAITING-->其他線程都BLOCKED-->開啓新線程-->頻繁引起GC-->佔用大量CPU-->應用掛起
後來對全部涉及到數據庫鏈接的代碼進行排查,發現確實有幾個地方作完數據庫操做之後,沒有釋放鏈接。把這部分代碼改掉,從新啓動JBOSS,沒有再出現JBOSS掛起的現象