http://sunnylocus.iteye.com/blog/538282
當一個單線程化的控制臺程序因為未捕獲的異常終止的時候,程序停止運行,并生了棧追蹤,這與典型的程序輸出不同,當一個程序發生了異常說明有不穩定的因素存在。如果并發程序中線程失敗就沒那么容易發現了。棧追蹤可能會從控制臺輸出,但是沒有人會去一直在看控制臺,并且,當線程失敗的時候,應用程序可能看起來仍在工作。就象程序能跑在50個線程池上,也能夠跑在49個線程的線程池上,區別在于50個人干的活要比49個人干的活要多的多。
導致線程死亡的的最主要的原因是RuntimeException。因為這些異常表明一個程序錯誤或者不可修復的錯誤,它們不著順著棧的調用傳遞,此時,默認的行為是在控制臺打印棧追蹤的信息,并終止線程。
我們舉個例子,將奴隸主比作是你寫的程序,奴隸比作是線程。假如你是奴隸主,你手下有5名奴隸,你分派他們一項任務去將不斷開采來的石頭搬到某個地方。如果中途有奴隸逃跑,那么搬運石頭的效率就會下降,如果你沒有措施發現奴隸逃跑,最后連一個奴隸也沒有了,沒有人再去搬石頭了。當你發現程序有問題,比如程序剛啟動的時候處理速度很快,以后越跑越慢,最后完全停止了,你可能不知道問題出在哪兒,其實這都是因為線程泄露引起的。想解決奴隸逃跑問題,不難,給每個奴隸戴上個報警器,一逃跑報警器就給你發信息,告訴哪個奴隸,因為什么原因逃跑了,可以根據需要再增加一名奴隸,讓搬石頭的奴隸數量始終維持在5名或著將信息記錄到文件,便于分析導致線程泄露的原意改進程序。ok,我們用代碼說話
1、定義好報警器
- package com.bill99.thread.test;
- import java.lang.Thread.UncaughtExceptionHandler;
- //將泄露的線程信息輸出到控制臺
- public class UEHLogger implements UncaughtExceptionHandler {
- public void uncaughtException(Thread t, Throwable e) {
- System.out.println(String.format("不好了,有奴隸逃跑了!奴隸姓名:%1$s,編號:%2$s,逃跑原因:%3$s", t.getName(),t.getId(),e.getMessage()));
- System.out.println("還剩"+HelotPool.helotPool.getActiveCount()+"個奴隸");
- }
- }
2、我們先建立一個奴隸工廠,每名奴隸出工廠的時候都會有一個報警器
- package com.bill99.thread.test;
- import java.util.concurrent.ThreadFactory;
- //奴隸制造工廠
- public class HelotFactory implements ThreadFactory {
- private volatile int helotId=0;//奴隸編號
- //產生一個新奴隸
- public Thread newThread(Runnable r) {
- Thread helotThread = new Thread(r);
- helotThread.setName("helot-Thread-"+gethelotId());//設置奴隸姓名
- helotThread.setUncaughtExceptionHandler(new UEHLogger());//UEHLogger就是報警器
- return helotThread;
- }
- private int gethelotId(){
- return ++helotId;
- }
- }
3、奴隸逃跑測試,看看是否會觸發報警器
- package com.bill99.thread.test;
- import java.util.concurrent.ThreadFactory;
- //奴隸逃跑測試
- public class HelotEscapeTest {
- private ThreadFactory factory = new HelotFactory();
- private Thread helotThread = null;
- public HelotEscapeTest(){
- Runnable task = new Runnable() {
- int stoneNum=1;
- public void run() {
- while(!Thread.interrupted()){
- System.out.println(helotThread.getName()+" 搬第"+stoneNum+"塊石頭..");
- stoneNum++;
- try{
- Thread.sleep(500);
- } catch(InterruptedException e){e.printStackTrace();}
- if(stoneNum>100){
- throw new RuntimeException("又餓又累沒力氣搬石頭了");
- }
- }
- }
- };
- helotThread = factory.newThread(task);
- }
- //開始干活
- public void startWork(){
- helotThread.start();
- }
- public static void main(String[] args) {
- HelotEscapeTest test = new HelotEscapeTest();
- test.startWork();
- }
- }
運行程序后,奴隸在搬完100塊石頭后不干了,報警器給出提示信息
不好了,有奴隸逃跑了!奴隸姓名:helot-Thread-1,編號:7,逃跑原因:又餓又累沒力氣搬石頭了
這樣我就能找出線程泄露的原因了。如果在線程池中發生了泄露是否也能記錄?
4、奴隸池測試
- package com.bill99.thread.test;
- import java.util.Random;
- import java.util.concurrent.BlockingQueue;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadFactory;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- public class HelotPoolTest {
- public static ThreadPoolExecutor helotPool;
- private int helotNum= 5; //奴隸數
- private Random random = new Random(100);
- private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
- public HelotPoolTest() {
- ThreadFactory factory = new HelotFactory();
- helotPool = new ThreadPoolExecutor( helotNum,helotNum,1000,TimeUnit.SECONDS,queue,factory);
- }
- //分配任務
- public void assignTask(){
- final int MAX=100;
- final int MIN=10;
- Runnable task = null;
- for(int j=0;j<20;j++){
- task = new Runnable() {
- int stoneNum = random.nextInt(MAX - MIN + 1) + MIN;// 生成10~100范圍的隨機數
- public void run() {
- for (int i = 1; i <= stoneNum; i++) {
- System.out.println(Thread.currentThread().getName() + " 搬完"+ i + "塊石頭");
- if (i == 60) {
- throw new RuntimeException("搬完第60塊石頭不干了");
- }
- try{
- Thread.sleep(100);//休息下
- }catch(InterruptedException e){e.printStackTrace();}
- }
- }
- };
- queue.add(task);
- }
- }
- //開始干活
- public void startWork(){
- helotPool.prestartAllCoreThreads();
- }
- public static void main(String[] args) {
- HelotPoolTest pool = new HelotPoolTest();
- pool.assignTask();
- pool.startWork();
- }
- }
程序啟動,用Jprofiler監控,剛啟動時會有5名奴隸干活不一會就會有線程退出,最后5個線程全部退,報警器對每個線程退出都能記錄到導致線程退出的原因。標準的Executor實現是:
在需求不高時回收空閑的線程,在需求增加時添加新的線程,如果任務拋出了異常,就會用一個全新的工作線程取代出錯的那個。
JDK文檔是這么說的,不過通過Jprofiler監控只有在5名奴隸全部逃跑,沒人干活的時候ThreadPoolExecutor才會生成一個新線程繼續搬石頭,并不是只要一個線程退出就會馬上生成新線程去代替。
Author: orangelizq
email: orangelizq@163.com
|
|