Java 如何正確停止一個線程
阿新 • • 發佈:2017-10-16
str day ble const vol 監聽 mem oracl error
自己在做實驗性小項目的時候,發現自己遇到一個問題:如何控制線程的"死亡"?
首先,如何開啟一個線程呢?
最簡單的代碼:
1 public class Main { 2 3 public static void main(String[] args) { 4 5 Thread thread = new Thread(new Runnable() { 6 @Override 7 public void run() { 8 System.out.println("當前線程:" + Thread.currentThread() + ",當前時間戳:" + System.currentTimeMillis());9 } 10 }); 11 12 thread.start(); 13 14 try { 15 Thread.sleep(1000L); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 System.out.println("主線程結束"); 20 } 21 }
很簡單,調用.start()方法,這個線程就會啟動.
那麽怎樣主動去停止一個線程呢?要解答這個問題,首先要考慮:為什麽要結束一個線程.
理由如下:
- 線程是JVM寶貴的資源,有的線程會長時間占用資源.
- 一些業務邏輯下會出現一個線程從邏輯上完全沒有意義(比如一個定時器,調用了結束的方法),確實需要去停止.
結束一個線程有一個最基本的方法:Thread.stop()方法:
1 @Deprecated 2 public final void stop() { 3 SecurityManager security = System.getSecurityManager(); 4 if (security != null) {5 checkAccess(); 6 if (this != Thread.currentThread()) { 7 security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); 8 } 9 } 10 // A zero status value corresponds to "NEW", it can‘t change to 11 // not-NEW because we hold the lock. 12 if (threadStatus != 0) { 13 resume(); // Wake up thread if it was suspended; no-op otherwise 14 } 15 16 // The VM can handle all thread states 17 stop0(new ThreadDeath()); 18 }
但是這個方法已經是:@Deprecated的了,也就是被建議不要使用的方法.
為什麽不建議使用這個方法的官方說明: http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
實際上,我結合自己經驗提出以下幾點:
- 線程會直接停掉,按照代碼邏輯要釋放的資源,要調用的接口可能不會被運行(finally塊的代碼還是會執行)
- 會破壞鎖,導致線程不安全(停掉一個線程就會釋放它持有的鎖,但不能保證邏輯上)
這兩點都是非常嚴重的問題了.即使再小心,去調用stop()也會導致各種各樣的問題.
如果不能"硬"實現結束線程,那麽就可以考慮下"軟"實現.
首先,導致一個線程長時間運行的原因無非有這麽幾個:
- 代碼邏輯混亂\業務復雜,寫了一個很長的run()方法
- 長時間睡眠
- 循環
對於這第一種嘛,只能從代碼結構上優化了.
而這第二種,就要使用Thread.interrupt()方法了.這個方法喚醒睡眠中的線程:
1 public class Main { 2 3 public static void main(String[] args) { 4 5 Thread thread = new Thread(new Runnable() { 6 @Override 7 public void run() { 8 try { 9 TimeUnit.DAYS.sleep(1L); 10 System.out.println("睡眠結束"); 11 } catch (Exception e) { 12 System.out.println("異常:" + e); 13 } finally { 14 System.out.println("finally塊被執行"); 15 } 16 } 17 }); 18 19 thread.start(); 20 21 if (!thread.isInterrupted()) { 22 thread.interrupt(); 23 } 24 System.out.println("主線程結束"); 25 } 26 }
在bio的服務端裏面,會阻塞當前線程.監聽的端口有消息,才會繼續執行,而如果沒有人連接,就需要通過這種方式來打斷正在監聽的線程.
對於這第三種,需要通過輪詢標誌位來控制退出.自己寫的定時器代碼:
1 public class TimerImpl implements Timer { 2 3 private static final Logger logger = LoggerFactory.getLogger(TimerImpl.class); 4 5 // 定時器線程 6 private TimerThread timerThread = null; 7 8 private volatile Boolean running = false; 9 10 private Handler taskHandler; 11 12 private Long time; 13 14 private TimeUnit unit; 15 16 @Override 17 public void start() throws Exception { 18 19 // 給參數生成本地固定拷貝,以確保在運行過程中,不會給參數的改變幹擾 20 Handler curTask = taskHandler.getClass().newInstance(); 21 Long curTime = new Long(time); 22 TimeUnit curUnit = unit.getClass().newInstance(); 23 24 // 檢查 25 if (ParameterUtil.checkNull(curTask, curTime, curUnit)) { 26 throw new Exception("定時器參數配置錯誤"); 27 } 28 29 if (!running) { 30 synchronized (running) { 31 if (!running) { 32 timerThread = new TimerThread(); 33 34 timerThread.setTaskHandler(curTask); 35 timerThread.setTime(curTime); 36 timerThread.setUnit(curUnit); 37 38 timerThread.start(); 39 running = true; 40 } 41 } 42 } 43 } 44 45 @Override 46 public void stop() throws Exception { 47 48 if (!running) { 49 throw new Exception("定時器尚未開始"); 50 } 51 52 synchronized (running) { 53 if (running) { 54 // 標誌位 55 timerThread.cancel(); 56 // 打斷睡眠 57 if (!timerThread.isInterrupted()) { 58 timerThread.interrupt(); 59 } 60 running = false; 61 } 62 } 63 } 64 65 private class TimerThread extends Thread { 66 67 private volatile boolean stop = false; 68 69 private Handler taskHandler; 70 71 private Long time; 72 73 private TimeUnit unit; 74 75 @Override 76 public void run() { 77 78 // circle 79 while (!stop) { 80 81 // sleep 82 try { 83 unit.sleep(time); 84 } catch (InterruptedException e) { 85 logger.info("定時線程被打斷,退出定時任務"); 86 stop = true; 87 return; 88 } 89 90 // do 91 try { 92 taskHandler.execute(); 93 } catch (Exception e) { 94 logger.error("handler執行異常:{}", this.getClass(), e); 95 } 96 } 97 } 98 99 public void cancel() { 100 stop = true; 101 } 102 103 public Handler getTaskHandler() { 104 return taskHandler; 105 } 106 107 public void setTaskHandler(Handler taskHandler) { 108 this.taskHandler = taskHandler; 109 } 110 111 public Long getTime() { 112 return time; 113 } 114 115 public void setTime(Long time) { 116 this.time = time; 117 } 118 119 public TimeUnit getUnit() { 120 return unit; 121 } 122 123 public void setUnit(TimeUnit unit) { 124 this.unit = unit; 125 } 126 } 127 128 @Override 129 public void setTimer(Long time, TimeUnit unit) { 130 this.time = time; 131 this.unit = unit; 132 } 133 134 @Override 135 public void setHandler(Handler handler) { 136 this.taskHandler = handler; 137 } 138 139 }
想要停止一個線程的方法是有的,但是會麻煩一些.
Java 如何正確停止一個線程