Java 如何正确停止一个线程
自己在做实验性小项目的时候,发现自己遇到一个问题:如何控制线程的"死亡"?
首先,如何开启一个线程呢?
最简单的代码:
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 }
想要停止一个线程的方法是有的,但是会麻烦一些.