[改善Java代码]不使用stop方法停止线程
线程启动完毕后,在运行可能需要终止,Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:
(1)stop方法是过时的
从Java编码规则来说,已经过时的方式不建议采用.
(2)stop方法会导致代码逻辑不完整
stop方法是一种"恶意" 的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的.
看如下代码:
- public class Client {
- public static void main(String[] args) throws Exception {
- // 子线程
- Thread thread = new Thread() {
- @Override
- public void run() {
- try {
- // 该线程休眠1秒
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- //异常处理
- }
- System.out.println("此处代码不会执行");
- }
- };
- // 启动线程
- thread.start();
- // 主线程休眠0.1秒
- Thread.sleep(100);
- // 子线程停止
- thread.stop();
- }
- }
这段代码的逻辑,子线程是一个匿名内部类,它的run方法在执行时会休眠1秒钟,然后再执行后续的逻辑,而主线程则是休眠0.1秒后终止子线程的运行,也就是说,JVM在执行thread.stop()时,子线程还在执行sleep(1000),此时stop方法会清除栈内信息,结束该线程,这也导致了run方法的逻辑不完整,输出语句println代表的是一段逻辑,可能非常重要,比如子线程的主逻辑,资源回收,情景初始化等等,但是因为stop线程了,这些就都不再执行了,于是就产生了业务逻辑不完整的情况.
这是极度危险的,因为我们不知道子线程会在什么时候停止,stop连基本的逻辑完整性都无法保证,而且此种操作也是非常隐蔽的,子线程执行到何处会被关闭很难定位,这为以后的维护带来了很多的麻烦.
(3)stop方法会破坏原子逻辑
多线程为了解决共享资源抢占的问题,使用了锁的概念,避免资源不同步,但是正是因为此原因,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损.例如 有这样一段程序:
- public class Client {
- public static void main(String[] args) {
- MultiThread t = new MultiThread();
- Thread t1 = new Thread(t);
- // 启动t1线程
- t1.start();
- for (int i = 0; i < 5; i++) {
- new Thread(t).start();
- }
- // 停止t1线程
- t1.stop();
- }
- }
- class MultiThread implements Runnable {
- int a = 0;
- @Override
- public void run() {
- // 同步代码块,保证原子操作
- synchronized ("") {
- // 自增
- a++;
- try {
- // 线程休眠0.1秒
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 自减
- a--;
- String tn = Thread.currentThread().getName();
- System.out.println(tn + ":a =" + a);
- }
- }
- }
MultiThread实现了Runnable接口,具备多线程的能力,run方法中加入了synchronized代码块,表示内部是原子逻辑,a的值会先增加后减少,按照synchronized的规则,无论启动多少个线程,打印出来的结果都应该是a=0,
但是如果有一个正在执行的线程被stop,就会破坏这种原子逻辑.(上面main方法中代码)
首先说明的是所有线程共享了 一个MultThread的实例变量t,其次由于在run方法中加入了同步代码块,所以只能有一个线程进入到synchronized块中.
此段代码的执行顺序如下:
1)线程t1启动,并执行run方法,由于没有其他线程同步代码块的锁,所以t1线程执行自加后执行到sleep方法开始休眠,此时a=1.
2)JVM又启动了5个线程,也同时运行run方法,由于synchronized关键字的阻塞作用,这5个线程不能执行自增和自减操作,等待t1线程释放线程锁.
3)主线程执行了t1.stop方法,终止了t1线程,注意由于a变量是线程共享的,所以其他5个线程获得的a变量也是1.
4)其他5个线程获得CPU的执行机会,打印出a的值.
结果是:
- Thread-5:a =1
- Thread-4:a =1
- Thread-3:a =1
- Thread-2:a =1
- Thread-1:a =1
原本期望synchronized同步代码块中的逻辑都是原子逻辑,不受外界线程的干扰,但是结果却出现原子逻辑被破坏的情况,这也是stop方法被废弃的一个重要原因:破坏了原子逻辑.
既然终止一个线程不能用stop方法,那怎样才能终止一个正在运行的线程呢?
使用自定义的标志位决定线程的执行情况,代码如下:
- import java.util.Timer;
- import java.util.TimerTask;
- public class Client {
- public static void main(String[] args) throws InterruptedException {
- final SafeStopThread sst = new SafeStopThread();
- sst.start();
- //0.5秒后线程停止执行
- new Timer(true).schedule(new TimerTask() {
- public void run() {
- sst.terminate();
- }
- }, 500);
- }
- }
- class SafeStopThread extends Thread {
- //此变量必须加上volatile
- private volatile boolean stop = false;
- @Override
- public void run() {
- //判断线程体是否运行
- while (stop) {
- // Do Something
- System.out.println("Stop");
- }
- }
- //线程终止
- public void terminate() {
- stop = true;
- }
- }
在线程主题中判断是否需要停止运行,即可保证线程体的逻辑完整性而且也不会破坏原值逻辑.
Thread还提供了一个interrupt中断线程的方法,这个不是过时的方法,是否可以使用这个中断线程?
很明确的说,interrupt不能终止一个正在执行着的线程,它只是修改中断标志位而已.例如:
- public class Client {
- public static void main(String[] args) {
- Thread t1 = new Thread() {
- public void run() {
- //线程一直运行
- while (true) {
- System.out.println("Running……");
- }
- }
- };
- // 启动t1线程
- t1.start();
- System.out.println(t1.isInterrupted());//false
- // 中断t1线程
- t1.interrupt();
- System.out.println(t1.isInterrupted());//true
- }
- }
执行这段代码,会一直有Running在输出,永远不会停止,执行了interrupt没有任何的变化,那是因为interrupt方法不能终止一个线程状态,它只会改变中断标志位.
在t1.interrupt()前后加上了t1.isInterrupted()会发现分别输出的是false和true.
如果需要终止该线程,还需要执行进行判断,例如我们可以使用interrupt编写出更加简洁,安全的终止线程的代码:
- import java.util.Timer;
- import java.util.TimerTask;
- public class Client {
- public static void main(String[] args) throws InterruptedException {
- final SafeStopThread sst = new SafeStopThread();
- sst.start();
- //0.5秒后线程停止执行
- new Timer(true).schedule(new TimerTask() {
- public void run() {
- sst.interrupt();
- }
- }, 500);
- }
- }
- class SafeStopThread extends Thread {
- @Override
- public void run() {
- //判断线程体是否运行
- while (!isInterrupted()) {
- // Do Something
- }
- }
- }
总之,如果期望终止一个正在运行的线程,则不能使用已经过时的stop方法,需要执行编码实现.这样保证原子逻辑不被破坏,代码逻辑不会出现异常.
当然还可以使用线程池,比如ThreadPoolExecutor类,那么可以通过shutdown方法逐步关闭线程池中的线程,它采用的是比较温和,安全的关闭线程方法,完全不会产生类似stop方法的弊端.
[改善Java代码]不使用stop方法停止线程的更多相关文章
- [改善Java代码]覆写equals方法必须覆写hashCode方法
覆写equals方法必须覆写hashCode方法,这条规则基本上每个Javaer都知道,这也是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢?本建议就来解释该问题,我们先 ...
- [改善Java代码]覆写equals方法时不要识别不出自己
建议45: 覆写equals方法时不要识别不出自己 我们在写一个JavaBean时,经常会覆写equals方法,其目的是根据业务规则判断两个对象是否相等,比如我们写一个Person类,然后根据姓名判断 ...
- Eclipse远程调试Java代码的三种方法
Eclipse远程调试Java代码的三种方法, 第1种方法是用来调试已经启动的Java程序,Eclipse可以随时连接到远程Java程序进行调试, 第2种方法可以调试Java程序启动过程,但是Ecli ...
- [改善Java代码]推荐覆写toString方法
建议49: 推荐覆写toString方法 为什么要覆写toString方法,这个问题很简单,因为Java提供的默认toString方法不友好,打印出来看不懂,不覆写不行,看这样一段代码: public ...
- [改善Java代码]不推荐覆写start方法
多线程比较简单的方式是继承Thread类,然后覆写run()方法,在客户端程序中通过调用对象的start方法即可启动一个线程,这个是多线程程序的标准写法. 错误代码: public class Cli ...
- [改善Java代码]易变业务使用脚本语言编写
建议16: 易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP.Ruby.Groovy.JavaScript等,这些“入侵者”都有一个共同特征:全是同一类语言—脚本语言,它们 ...
- [改善Java代码]优先选择线程池
在Java1.5之前,实现多线程编程比较麻烦,需要自己启动线程,并关注同步资源,防止线程死锁等问题,在1.5版本之后引入了并行计算框架,大大简化了多线程开发. 我们知道线程有5个状态:新建状态(New ...
- [改善Java代码]在接口中不要存在实现代码
第3章 类.对象及方法 书读得多而不思考,你会觉得自己知道的很多. 书读得多而思考,你会觉得自己不懂的越来越多. —伏尔泰 在面向对象编程(Object-Oriented Programming,O ...
- [改善Java代码]使用构造块精炼程序
建议36: 使用构造代码块精炼程序 什么叫代码块(Code Block)?用大括号把多行代码封装在一起,形成一个独立的数据体,实现特定算法的代码集合即为代码块,一般来说代码块是不能单独运行的,必须要有 ...
随机推荐
- 【转】nginx的优缺点
原博文出自于:http://blog.csdn.net/a454211787/article/details/22494485 感谢! 1.nginx相对于apache优点: 轻量级同样起we ...
- Apache Spark Streaming的适用场景
使用场景: Spark Streaming 适合需要历史数据和实时数据结合进行分析的应用场景,对于实时性要求不是特别高的场景也能够胜任.
- JNI调用测试
有需求使用JNI调用,籍着这个机会按照<Linux下测试Java的JNI(Java Native Interface)>上进行了下测试. 这篇文章记录得很清楚了,对原理未做深入的分析,希望 ...
- Jvm基础(2)-Java内存模型
Jvm基础(2)-Java内存模型 主内存和工作内存 Java内存模型包括主内存和工作内存两个部分:主内存用来存储线程之间的共享变量:而工作内存中存储每个线程的相关变量. 如下图所示: 需要注意的是: ...
- MD5验证工具:md5sum
linux 下 shell命令 ,制作md5码 也用于软件的md5校验 MD5算法常常被用来验证网络文件传输的完整性,防止文件被人篡改.MD5 全称是报文摘要算法(Message-Digest ...
- 山东理工大学ACM平台题答案关于C语言 1137 C/C++经典程序训练7---求某个范围内的所有素数
C/C++经典程序训练7---求某个范围内的所有素数 Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^ 题目描述 求小于n的所有素数,按照每行 ...
- python的socket里 gethostbyname 与 gethostbyname_ex 的区别
python里有一个模块,叫socket,提供了BSD socket 的通信接口,在看了这个模块之后,我发现了两个很相似的函数------gethostbyname 和gethostbyname_ex ...
- InvokeHelper,让跨线程访问/修改主界面控件不再麻烦(转)
http://bbs.csdn.net/topics/390162519 事实上,本文内容很简单且浅显,所以取消前戏,直接开始.. 源代码:在本文最后 这里是一张动画,演示在多线程(无限循环+Thre ...
- 【M7】千万不要重载&&,||和,操作符
1.C++对于真假值表达式采用“骤死式”评估方法,比如&&,||. if( p!=NULL && strlen(p)>10) 如果p为NULL,后面的strl ...
- Codeforces Gym 100513F F. Ilya Muromets 线段树
F. Ilya Muromets Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100513/probl ...