java多線程解決應用掛死的問題


這兩天為了定位JBOSS老是掛死的問題,學習了一下JAVA多線程方面的知識,在此總結一下 

1、在Java程序中,JVM負責線程的調度。線程調度是指按照特定的機制為多個線程分配CPU的使用權。 
調度的模式有兩種:分時調度和搶占式調度。分時調度是所有線程輪流獲得CPU使用權,並平均分配每個線程占用CPU的時間;搶占式調度是根據線程的優先級別來獲取CPU的使用權。JVM的線程調度模式采用了搶占式模式。 

2、Thread類實際上也是實現了Runnable接口的類。 
在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然后調用Thread對象的start()方法來運行多線程代碼。 
實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是擴展Thread類還是實現Runnable接口來實現多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。 

3、JAVA多線程涉及到2個問題,一個是線程的調度,另一個是線程的同步 

4、線程的狀態有: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狀態,該線程結束 

5、當線程池中線程都具有相同的優先級,調度程序的JVM實現自由選擇它喜歡的線程。這時候調度程序的操作有兩種可能:一是選擇一個線程運行,直到它阻塞或者運行完成為止。二是時間分片,為池內的每個線程提供均等的運行機會。 

6、設置線程的優先級:線程默認的優先級是創建它的執行線程的優先級。可以更改線程的優先級。 

JVM從不會改變一個線程的優先級。然而,1-10之間的值是沒有保證的。一些JVM可能不能識別10個不同的值,而將這些優先級進行每兩個或多個合並,變成少於10個的優先級,則兩個或多個優先級的線程可能被映射為一個優先級。 

7、Thread.yield()方法作用是:暫停當前正在執行的線程對象,並執行其他線程。 
yield()應該做的是讓當前運行線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行機會。因此,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調度程序再次選中。 

結論:yield()從未導致線程轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。 

8、另一個問題是線程的同步,這個我感覺比調度更加復雜一些 

Java中每個對象都有一個“內置鎖”,也有一個內置的“線程表” 

當程序運行到非靜態的synchronized方法上時,會獲得與正在執行代碼類的當前實例(this實例)有關的鎖;當運行到同步代碼塊時,獲得與聲明的對象有關的鎖 

釋放鎖是指持鎖線程退出了synchronized方法或代碼塊。 

當程序運行到synchronized同步方法或代碼塊時對象鎖才起作用。 

一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。 

9、當提到同步(鎖定)時,應該清楚是在哪個對象上同步(鎖定)? 

10、 

obj.wait() 
obj.notify() 
obj.notifyAll() 

關於這3個方法,有一個關鍵問題是: 

必須從同步環境內調用wait()、notify()、notifyAll()方法。只有擁有該對象的鎖的線程,才能調用該對象上的wait()、notify()、notifyAll()方法 

與每個對象具有鎖一樣,每個對象也可以有一個線程列表,他們等待來自該對象的通知。線程通過執行對象上的wait()方法獲得這個等待列表。從那時候起,它不再執行任何其他指令,直到調用對象的notify()方法為止。如果多個線程在同一個對象上等待,則將只選擇一個線程(不保證以何種順序)繼續執行。如果沒有線程等待,則不采取任何特殊操作。 

11、下面貼幾個代碼實例,配合jstack命令說明一下 

Java代碼   收藏代碼
  1. public class ThreadA {  
  2.   
  3.     public static void main(String[] args) {  
  4.           
  5.         ThreadB b = new ThreadB();// ThreadB status: new  
  6.       
  7.         b.start();// ThreadB status: runnable  
  8.           
  9.         synchronized (b) {  
  10.             try {                 
  11.                 System.out.println("等待對象b完成計算。。。");  
  12.                 Thread.sleep(60000);  
  13.                 b.wait();  
  14.             } catch (InterruptedException e) {  
  15.                 e.printStackTrace();  
  16.             }  
  17.             System.out.println("b對象計算的總和是:" + b.total);  
  18.         }  
  19.     }  
  20.   
  21. }  
  22.   
  23. public class ThreadB extends Thread {  
  24.   
  25.     int total;  
  26.   
  27.     public void run() {  
  28.         synchronized (this) {  
  29.             for (int i = 0; i < 101; i++) {  
  30.                 total += i;  
  31.             }  
  32.             notifyAll();  
  33.         }  
  34.     }  
  35.   
  36. }   


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狀態 

Java代碼   收藏代碼
  1. public class ThreadA {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         ThreadB b = new ThreadB();// ThreadB status: new  
  6.   
  7.         b.start();// ThreadB status: runnable  
  8.   
  9.         synchronized (b) {  
  10.               
  11.             try {  
  12.                 System.out.println("等待對象b完成計算。。。");  
  13.                 b.wait();  
  14.             } catch (InterruptedException e) {  
  15.                 e.printStackTrace();  
  16.             }  
  17.             System.out.println("b對象計算的總和是:" + b.total);  
  18.         }  
  19.     }  
  20.   
  21. }  
  22.   
  23. public class ThreadB extends Thread {  
  24.   
  25.     int total;  
  26.   
  27.     public void run() {  
  28.   
  29.         synchronized (this) {  
  30.   
  31.             try {  
  32.                 Thread.sleep(60000);  
  33.             } catch (InterruptedException e) {  
  34.                 e.printStackTrace();  
  35.             }  
  36.   
  37.             for (int i = 0; i < 101; i++) {  
  38.                 total += i;  
  39.             }  
  40.             notifyAll();  
  41.         }  
  42.     }  
  43.   
  44. }  


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狀態 

Java代碼   收藏代碼
  1. public class ThreadA {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         ThreadB b = new ThreadB();// ThreadB status: new  
  6.   
  7.         b.start();// ThreadB status: runnable  
  8.   
  9.         synchronized (b) {  
  10.   
  11.             try {  
  12.                 System.out.println("等待對象b完成計算。。。");  
  13.                 b.wait();// ThreadB status: running  
  14.             } catch (InterruptedException e) {  
  15.                 e.printStackTrace();  
  16.             }  
  17.             System.out.println("b對象計算的總和是:" + b.total);  
  18.         }  
  19.     }  
  20.   
  21. }  
  22.   
  23.   
  24. public class ThreadB extends Thread {  
  25.   
  26.     int total;  
  27.   
  28.     public void run() {  
  29.   
  30.         synchronized (this) {  
  31.   
  32.             for (int i = 0; i < 101; i++) {  
  33.                 total += i;  
  34.             }  
  35.   
  36.             notifyAll();  
  37.   
  38.             try {  
  39.                 System.out.println("我要睡了");  
  40.                 Thread.sleep(60000);  
  41.             } catch (InterruptedException e) {  
  42.                 e.printStackTrace();  
  43.             }  
  44.   
  45.         }  
  46.     }  
  47.   
  48. }  


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狀態 

12、當在對象上調用wait()方法時,執行該代碼的線程立即放棄它在對象上的鎖。然而調用notify()時,並不意味着這時線程會放棄其鎖。如果線程仍然在完成同步代碼,則線程在移出之前不會放棄鎖。因此,只要調用notify()並不意味着這時該鎖被釋放 

13、與線程休眠類似,線程的優先級仍然無法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的概率較大,優先級低的並非沒機會執行。 

14、在一個線程中開啟另外一個新線程,則新開線程稱為該線程的子線程,子線程初始優先級與父線程相同。 

15、JRE判斷程序是否執行結束的標准是所有的前台執線程行完畢了,而不管后台線程的狀態,因此,在使用后台線程時候一定要注意這個問題。 

16、下面說說我們這次JBOSS掛死問題的處理方法 

現象:系統運行一段時間之后,發現有幾個子系統無法訪問了,但是另外幾個可以。CPU占用達到100% 

觀察了一下,發現無法訪問的應用都部署在同一個JBOSS里,於是把該JBOSS的堆棧用jstack命令輸出 

發現里面有大量的線程處於BLOCKED狀態,均是在執行到c3p0的一個方法里的某一行時,BLOCKED住了 

於是下載c3p0的源碼,跟進去看了一下,這是一個同步方法,內部會去獲取數據庫連接,如果獲取到連接,就進行下一步操作,如果獲取不到,就執行sleep(long timeout)方法。 

反推一下,我猜測可能是這樣的: 

由於某段代碼沒有釋放數據庫連接-->連接池中的連接耗盡-->部分線程無限TIMED_WAITING-->其余線程都BLOCKED-->開啟新線程-->頻繁引發GC-->占用大量CPU-->應用掛起 

后來對所有涉及到數據庫連接的代碼進行排查,發現確實有幾個地方做完數據庫操作以后,沒有釋放連接。把這部分代碼改掉,重新啟動JBOSS,沒有再出現JBOSS掛起的現象 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM