搞过Java线程的人都知道,stop这个方法是臭名昭著了,早就被弃用了,但是现在任然有很多钟情与他的人,永远都放不下他,因为从他的字面意思上我们可以知道他貌似可以停止一个线程,这个需求是每个搞线程开发的人都想要的操作,但是他并非是真正意义上的停止线程,而且停止线程还会引来一些其他的麻烦事,下面就来详细的介绍一下这个方法的历史:

从SUN的官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事:

1. 即刻抛出ThreadDeath异常,在线程的run()方法内,任何一点都有可能抛出ThreadDeath Error,包括在catch或finally语句中。

2. 会释放该线程所持有的所有的锁,而这种释放是不可控制的,非预期的。

当线程抛出ThreadDeath异常时,会导致该线程的run()方法突然返回来达到停止该线程的目的。ThreadDetath异常可以在该线程run()方法的任意一个执行点抛出。但是,线程的stop()方法一经调用线程的run()方法就会即刻返回吗?

[java] view
plain
copy

  1. package com.threadstop.demo;
  2. public class ThreadStopTest {
  3. public static void main(String[] args) {
  4. try {
  5. Thread t = new Thread() {
  6. //对于方法进行了同步操作,锁对象就是线程本身
  7. public synchronized void run() {
  8. try {
  9. long start=System.currentTimeMillis();
  10. //开始计数
  11. for (int i = 0; i < 100000; i++)
  12. System.out.println("runing.." + i);
  13. System.out.println((System.currentTimeMillis()-start)+"ms");
  14. } catch (Throwable ex) {
  15. System.out.println("Caught in run: " + ex);
  16. ex.printStackTrace();
  17. }
  18. }
  19. };
  20. //开始计数
  21. t.start();
  22. //主线程休眠100ms
  23. Thread.sleep(100);
  24. //停止线程的运行
  25. t.stop();
  26. } catch (Throwable t) {
  27. System.out.println("Caught in main: " + t);
  28. t.printStackTrace();
  29. }
  30. }
  31. }

运行结果如下:

由于打印的数据太多了,就没有全部截图了,但是我们可以看到,调用了stop方法之后,线程并没有停止,而是将run方法执行完。那这个就诡异了,多次运行之后发现每次运行的结果都表明,工作线程并没有停止,而是每次都成功的数完数(执行完run方法),然后正常中止,而不是由stop()方法进行终止的。这个是为什么呢?根据SUN的文档,原则上只要一调用thread.stop()方法,那么线程就会立即停止,并抛出ThreadDeath error,查看了Thread的源代码后才发现,原先Thread.stop(Throwable
obj)
方法是同步的,而我们工作线程的run()方法也是同步,那么这样会导致主线程和工作线程共同争用同一个锁(工作线程对象本身),由于工作线程在启动后就先获得了锁,所以无论如何,当主线程在调用t.stop()时,它必须要等到工作线程的run()方法执行结束后才能进行,结果导致了上述奇怪的现象。

下面看一下stop的源码:

[java] view
plain
copy

  1. @Deprecated
  2. public final void stop() {
  3. stop(new ThreadDeath());
  4. }

再进到stop看:

[java] view
plain
copy

  1. @Deprecated
  2. public final synchronized void stop(Throwable obj) {
  3. if (obj == null)
  4. throw new NullPointerException();
  5. SecurityManager security = System.getSecurityManager();
  6. if (security != null) {
  7. checkAccess();
  8. if ((this != Thread.currentThread()) ||
  9. (!(obj instanceof ThreadDeath))) {
  10. security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
  11. }
  12. }
  13. // A zero status value corresponds to "NEW", it can't change to
  14. // not-NEW because we hold the lock.
  15. if (threadStatus != 0) {
  16. resume(); // Wake up thread if it was suspended; no-op otherwise
  17. }
  18. // The VM can handle all thread states
  19. stop0(obj);
  20. }

stop0(obj)是一个native方法,我们看到stop方法是同步的,而这个同步的锁对象正好也是线程本身,所以造成上面的现象。

把上述工作线程的run()方法的同步去掉,再进行执行,结果就如上述第一点描述的那样了,运行结果如下:

从结果中我们可以看到,调用stop方法会抛出一个ThreadDeath异常,这时候run方法也就执行结束了,线程就终止了,这种是用抛异常来结束线程的,但是这种抛出线程是不安全的,因为他不可控制,不知道到在run方法中的何处就可能抛出异常,所以是危险的。下面在看一下stop的这个隐患可能造成的影响:

接下来是看看当调用thread.stop()时,被停止的线程会不会释放其所持有的锁,看如下代码:

[java] view
plain
copy

  1. public static void main(String[] args) {
  2. <span style="white-space:pre">  </span>//定义锁对象
  3. final Object lock = new Object();
  4. //定义第一个线程,首先该线程拿到锁,而后等待3s,之后释放锁
  5. try {
  6. Thread t0 = new Thread() {
  7. public void run() {
  8. try {
  9. synchronized (lock) {
  10. System.out.println("thread->" + getName()  + " acquire lock.");
  11. sleep(3*1000);
  12. System.out.println("thread->" + getName() + " 等待3s");
  13. System.out.println("thread->" + getName()  + " release lock.");
  14. }
  15. } catch (Throwable ex) {
  16. System.out.println("Caught in run: " + ex);
  17. ex.printStackTrace();
  18. }
  19. }
  20. };
  21. //定义第二个线程,等待拿到锁对象
  22. Thread t1 = new Thread() {
  23. public void run() {
  24. synchronized (lock) {
  25. System.out.println("thread->" + getName()  + " acquire lock.");
  26. }
  27. }
  28. };
  29. //线程一先运行,先拿到lock
  30. t0.start();
  31. //而后主线程等待100ms,为了做延迟
  32. Thread.sleep(100);
  33. //停止线程一
  34. //t0.stop();
  35. //这时候在开启线程二
  36. t1.start();
  37. } catch (Throwable t) {
  38. System.out.println("Caught in main: " + t);
  39. t.printStackTrace();
  40. }
  41. }

运行结果如下:

从运行结果中我们可以看到,当没有进行t0.stop()方法的调用时, 可以发现,两个线程争用锁的顺序是固定的。这个现象是正常的。

下面我们把t0.stop注释的哪行,删除注释,调用t0.stop()方法,运行结果如下:

从运行结果中我们可以看到,调用了t0.stop()方法后,可以发现,t0线程抛出了ThreadDeath error并且t0线程释放了它所占有的锁。

从上面的程序验证结果来看,thread.stop()确实是不安全的。它的不安全主要是:释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

下面顺便说一下:

Java中多线程锁释放的条件:

1)执行完同步代码块,就会释放锁。(synchronized)

2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。(exception)

3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进入对象的等待池。(wait)

从上面的三点我就可以看到stop方法释放锁是在第二点的,通过抛出异常来释放锁,通过证明,这种方式是不安全的,不可靠的。

好吧,Thread中的stop方法就说到这里了,后续还会有关Thread中的一些方法的详解,一定要关注奥!

Java中的线程Thread方法之---stop()的更多相关文章

  1. Java中的线程Thread方法之---interrupt()

    前几篇都介绍了Thread中的几个方法,相信大家都发现一个相似点,那就是sleep,join,wait这样的阻塞方法都必须捕获一个InterruptedException异常,顾名思义就是一个线程中断 ...

  2. Java中的线程Thread方法之---suspend()和resume()

    前篇说到了Thread中的join方法,这一篇我们就来介绍一下suspend()和resume()方法,从字面意义上可以了解到这两个方法是一对的,suspend()方法就是将一个线程挂起(暂停),re ...

  3. Java中的线程Thread方法之---join()

    上一篇我们说到了Thread中的stop方法,这一篇我们再来看一下方法join的使用,那么方法Join是干啥用的? 简单回答,同步,如何同步? 怎么实现的? 下面将逐个回答. join方法从字面上的意 ...

  4. Java中的线程Thread总结

    首先来看一张图,下面这张图很清晰的说明了线程的状态与Thread中的各个方法之间的关系,很经典的! 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口. 要注意的是Threa ...

  5. Java中实现线程的方法

    Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用? 第一种:继承Thread类 new Thread(){}.start();这表示调 ...

  6. Java 中的线程 thread

    一.问:线程有哪些状态? new, runnable, running, waiting, dead 线程状态间的流转 二.问:线程实现方式? 实现 Runnable 接口,然后new Thread, ...

  7. JAVA中创建线程的三种方法及比较

    JAVA中创建线程的方式有三种,各有优缺点,具体如下: 一.继承Thread类来创建线程 1.创建一个任务类,继承Thread线程类,因为Thread类已经实现了Runnable接口,然后重写run( ...

  8. JAVA中创建线程池的五种方法及比较

    之前写过JAVA中创建线程的三种方法及比较.这次来说说线程池. JAVA中创建线程池主要有两类方法,一类是通过Executors工厂类提供的方法,该类提供了4种不同的线程池可供使用.另一类是通过Thr ...

  9. 【Java中的线程】java.lang.Thread 类分析

    进程和线程 联想一下现实生活中的例子--烧开水,烧开水时是不是不需要在旁边守着,交给热水机完成,烧开水这段时间可以去干一点其他的事情,例如将衣服丢到洗衣机中洗衣服.这样开水烧完,衣服洗的也差不多了.这 ...

随机推荐

  1. Android蓝牙自动配对Demo,亲测好使!!!(转)

    蓝牙自动配对,即搜索到其它蓝牙设备之后直接进行配对,不需要弹出配对确认框或者密钥输入框. 转载请注明出处http://blog.csdn.net/qq_25827845/article/details ...

  2. .babelrc配置例子

    { "presets":[ ["es2015",{"modlues":false}], "react", ], &quo ...

  3. clickhouse高可用-节点宕机数据一致性方案-热扩容

    1. 集群节点及服务分配 说明: 1.1. 在每个节点上启动两个clickhouse服务(后面会详细介绍如何操作这一步),一个数据分片,一个数据备份,为了确保宕机数据一致性,数据分片和数据备份不能同一 ...

  4. HTML标签分类总结

    块级元素和行内元素块级元素address - 地址blockquote - 块引用center - 举中对齐块dir - 目录列表div - 常用块级容易,也是css layout的主要标签dl - ...

  5. Android中的ListView的绘制过程中执行的方法

    首先,系统在绘制ListView之前, 将会先调用getCount方法来获取Item的个数.(如果getCount方法返回0的话,列表时不显示任何内容的) 之后每绘制一个 Item就会调用一次getV ...

  6. 使用HBuilder编辑器进行真机调试运行时提示Waiting for debugger!

    在使用HBuilder编辑器创建mui项目进行真机调试的时候,手机总是提示Waiting for debugger! 现在终于找到了解决办法: 手机 设置 -> 开发人员选项 -> USB ...

  7. Openfire部署(一)

    1.从官网下载openfire_4_1_4.tar.gz文件: 2.上传到linux上,解压缩 3.启动openfire [root@localhost opt]# cd openfire/bin [ ...

  8. STM32嵌入式开发学习笔记(七):串口通信(下)

    下面我们进行几个串口通信的实际应用. 实验一:发信实验,让开发板通过串口向电脑发送信息: #include <stdio.h> #include <stm32f10x.h> # ...

  9. 如何在普通用户权限cmd怎么使用命令行变为管理员权限

    这两天在弄MySql,由于我下载的是免安装版本,环境自己配置了一下.有强迫症不喜欢某些服务打开在我不需要的时候,我一般都设置为手动,但是每次使用数据库时都要使用net start mysql启动MyS ...

  10. 如何上传代码至GitHub

    环境准备: git GitHub账号 步骤: 1.登录github创建一个新的项目  参数说明: Repository name: 仓库名称 Description(可选): 仓库描述介绍 Publi ...