`

【Java】被废弃的线程方法

    博客分类:
  • Java
阅读更多

原文:《Java Thread Primitive Deprecation

 

为什么 Thread.stop 被废弃了?

Thread.stop 不安全

  • Stop 一个线程会导致它释放所持有的锁(monitor)。(ThreadDeatch 抛出后会释放 monitor 的锁。)
  • 这可能会导致之前被 monitor 保护的对象处于 不一致的状态,并被其它线程看到。也就是说,这些 对象被损坏 了。
  • 线程操作被损坏的对象会导致不可预料的行为。
  • 这些行为可能很微妙难以被察觉,也可能非常明显。与其它异常不同的是,ThreadDeatch 异常会悄悄地杀掉线程,用户感知不到程序已被损坏。这种程序损坏可能在错误发生后任何时候显现,甚至是几小时、几天。

 

为什么 我不能捕获 ThreadDeath 异常,并修复损坏的对象?

理论上,也许是可以的。但这会使编写多线程任务代码非常复杂,几乎不可能实现。因为:

  • 线程可能在任何地方抛出 ThreadDeatch 异常。必须非常详尽地分析所有同步方法和代码块,来处理这些情况。
  • 线程在处理第一个 ThreadDeatch 异常期间(catch 或 finally),可能会再次抛出 ThreadDeatch 异常(嵌套异常)。你的异常处理操作必须不断重复,直到成功。这会导致代码非常复杂。

所以这种方案是不切实际的!

 

Thread.stop(Throwable) 如何?

上述提到的所有问题它都有。此外,此方法一般用于生成一个 目标线程未准备好处理的 异常(包括目标线程不可能抛出的一些 受检异常)。如,以下示例中方法的行为等价于 throw 操作,但它规避了编译器对受检异常的检查保障 —— 调用方法必须声明所有可能抛出的受检异常。
(即,这是一种不安全的错误代码模式。)

void sneakyThrow(Throwable t) {
  Thread.currentThread().stop(t);
}

 

我该用什么方法替代 Thread.stop ?

绝大多数使用 stop 的代码应改为:

  • 修改一些标记性变量的值 以指示目标线程应停止运行;
  • 目标线程应定期检查这些标记变量;当检测到变量值指示应停止运行时,再有条不紊地从运行方法中自行返回
  • 为了保证“停止请求”通信正确,此标记变量必须是 volatile 或 对其访问操作进行同步。

示例:假设你的程序中有以下三个方法 start、stop、run。

private Thread blinker;

void start() {
  blinker = new Thread(this);
  blinker.start();
}

void stop() {
  blinker.stop(); // 不安全的操作
}

void run() {
  while (true) {
    try {
      Thread.sleep(interval);
    } catch (InterruptedException e) {
    }
    repaint();
  }
}

 

那么你可以改造自己的 stop 和 run,避免使用 Thread.stop:

private volatile Thread blinker;

void stop() {
  blinker = null;
}

void run() {
  Thread thisThread = Thread.currentThread;
  while (blinker == thisThread) { // 每次迭代前先检查标记变量
    try {
      Thread.sleep(interval);
    } catch (InterruptedException e) {
      repaint();
    }
  }
}

 

如何终止一个等待了很长时间的线程?(如,等待输入内容很久的线程)

Thread.interrupt 方法就是用来干这事的。
可以结合上述 “基于状态信号机制” 使用:状态变化后调用此方法,以中断等待。如:

void stop() {
  Thread moribund = waiter;
  waiter = null;
  moribund.interrupt();
}

 

这项技术中,非常关键的一点是,如果方法捕获到中断异常,但又不想立即处理它,那就得 “再次声明” 此异常。(因为 InterruptedException 是受检异常。)
之所以说是 “再次声明” 而不是 “再次抛出”,是因为并不是所有情况下都能再次抛出此异常。
如果方法捕获了 InterruptedException 且未声明会抛出此异常,那就需要 “重新中断自己”:

Thread.currentThread().interrupt();

这样就可以再次让线程抛出 InterruptedException。

 

如果线程没有响应 Thread.interrupt 该怎么办?

在某些情况下,你可以使用一些特定于应用程序的技巧。例,如果线程正在等待一个 socket,你可以将该 socket 关闭,从而让线程立即返回。
 

不幸的是,这方面真的没有通用的技术
 

需要说明的是,在所有场景中,如果一个等待线程没有响应 Thread.interrupt,那么它也不会响应 Thread.stop。如,恶意的拒绝服务攻击(DoS),某些 stop/interrupt 无法正确工作的 IO操作 等。

 

为什么 Thread.suspend 和 Thread.resume 被废弃了?

Thread.suspend 容易引发死锁
 

如果目标线程持有某项资源的锁,然后被挂起(suspend),那么在它被恢复前(resume)其它线程无法访问该资源。
此时,如果那个可以恢复目标线程的另一个线程需要 “先获取该资源的锁 - 再恢复目标线程”,那么就形成了死锁。
这种死锁通常表现为“冻结”进程。

 

我该用什么替代 Thread.suspend 和 Thread.resume ?

类似 Thread.stop,谨慎的做法是:

  • 设置一个标记性变量来表示期望的线程状态;
  • 目标线程轮询该变量;当期望状态是“挂起”时,线程通过 Object.wait 方法将自己挂起等待;
  • 当期望目标线程继续运行时,通过 Object.notify 方法唤醒它。

例,你的程序中有一个事件处理方法 mousePressed 用于切换 blinker 线程的状态:

boolean threadSuspended;
void mousePressed(MouseEvent e) {
  e.consume();

  if (threadSuspended) {
    blinker.resume();
  } else {
    blinker.suspened(); // 容易引发死锁
  }

  threadSuspended = !threadSuspended;
}

 

你可以改造为如下形式:

volatile boolean threadSuspended;

synchronized void mousePressed(MouseEvent e) {
  e.consume();

  threadSuspended = !threadSuspended;

  if (!threadSuspended) {
    notify();
  }
}

void run() {
  while (true) {
    try {
      Thread.sleep(interval);

      if (threadSuspended) {
        synchronized(this) {
          while (threadSuspended) {
            wait();
          }
        }
      }
    } catch (InterruptedException e) {
    }
    repaint();
  }
}

 

  • run 方法就是 blinker 线程运行的内容。
  • notify 和 wait 方法都被包在 synchronized 块中,保证它们的正确的执行顺序。这样可以避免因被挂起线程遗漏 notify 信号而导致无限挂起。
  • 标记变量 threadSuspended 是 volatile,以保证挂起请求的正常通信。
  • run 方法中的利用 双检锁 的形式来减少不必要的同步。

 

我能利用上述两个技术创建一个可以被 安全地 “stopped” 和 “suspended” 的线程吗?

可以,而且很简单。
 

比较微妙的是,另一个线程尝试停止目标线程时,目标线程可能已经处于挂起状态。
如果你的 stop 方法仅仅是更改 状态变量的值,那么目标线程是继续挂起(在monitor上等待),而不是期望的“优雅退出”。这会导致程序行为不稳定。
 

为了矫正这种状况,stop 方法必须确保目标线程能从挂起状态立即恢复
目标线程一旦恢复,必须能立即识别出自己已被标记为“需要终止”
实现示例:

private volatile Thread blinker;

void run() {
  Thread thisThread = Thread.currentThread();
  while (blinker == thisThread) { // 判断是否“需要终止”
    try {
      Thread.sleep(interval);

      synchronized(this) {
        // 只有当 不需要终止 且 期望挂起 时才会执行 wait
        while (threadSuspended && blinker==thisThread) {
          wait();
        }
      }
    } catch (InterruptedException e) {
    }
    repaint();
  }
}

synchronized void stop() {
  blinker = null; // 会被目标线程判定为“需要终止”
  notify();  // 立即唤醒目标线程
}

 

当然,你也可以在 stop 方法中调用 Thread.interrupt 来终止目标线程,这样就不需要调用 notify。
但 interrupt 方法也必须包含到 synchronized 中,确保目标线程不会错过 interrupt 信号。

 

Thread.destroy 如何?

Thread.destroy 从来都没有被实现过,且已经被废弃了。如果它被实现了,那么它会像 Thread.suspend 那样容易引发死锁。(事实上,它相当于 Thread.suspend,且没有后续的 Thread.resume)

 

为什么 Runtime.runFinalizersOnExit 被废弃了?

Runtime.runFinalizersOnExit 不安全
 

它可能会导致存活对象被finalize,且同时有其它线程正在操作这些对象,导致不稳定的行为或死锁。
 

虽然可以通过 Class 实现来避免存活对象被finalize,但最大多数程序员都不会怎么设计。他们都会假设对象被finalize时肯定已经死了。
而且此方法是设置 JVM 的一个标记变量,它会影响到所有类。让所有类的设计者都增加避免过早被finalize的防御肯定是不切实际的。

 

 

分享到:
评论

相关推荐

    Java中断线程的方法

    Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 这些终止线程运行的方法已经被废弃,使用它们是极端不安全的!  现在,如果你要安全有效地终止一个线程,应该采用以下这些方法: ...

    Java常见面试问题整理.docx

    Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 2.方法区(元空间):方法区用于存储已被虚拟机加载的类信息、常量、...

    android内存分析

    那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树...

    Java之IO流学习总结

    已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。 SequenceInputStream 可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO 包中去除,...

    02-VIP-Netty核心功能与线程模型精讲1

    Netty 现在都在用的是4.x,5.x版本已经废弃,Netty 4.x 需要JDK 6以上版本支持Netty的使用场景:1)互联网行业:在分布式系统中,各个节

    HashMap和Hashtable的区别

    Hashmap是开发中用的比较多的一种集合,是线程不安全的,Hashtable的方法上大多都加了synchronized所以是线程安全的,所以效率并不高。这也是它们最大的不同。  2.Hashtable继承的类和实现的接口:(Dictionary类...

    android内存及内存溢出分析详解

     那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树...

    Torch:[废弃]此仓库是一面镜子,请访问我们的新项目

    快速-简化逻辑并导入多线程计算。 Akarin仍在大量开发中,欢迎投入使用并在投入生产之前进行测试。 获取Akarin 下载 推荐地点 荣誉 检出最新版本的“工件”选项卡需要登录 通过以下电子邮件与我联系,或者如果您想...

    新版Android开发教程.rar

    � GPS 卫星导航功能,手机照相, MP3 ,蓝芽等均被列为 Android 所提供支持的基本选项。 � Android 的平台基本上是免费的,虽然有部份原生链接库会要求费用,但大部份是免权利金; Android 的 程序可以采用 JAVA ...

    程序员面试攻略 part1(共2个)

    10.13 面试例题:与线程有关的程序设计问题174 10.14 面试例题:废弃内存的自动回收175 10.15 面试例题:32位操作系统177 10.16 面试例题:网络性能177 10.17 面试例题:高速磁盘缓存177 10.18 面试例题:...

    程序员面试攻略part 2(共2个)

    10.13 面试例题:与线程有关的程序设计问题174 10.14 面试例题:废弃内存的自动回收175 10.15 面试例题:32位操作系统177 10.16 面试例题:网络性能177 10.17 面试例题:高速磁盘缓存177 10.18 面试例题:...

    e语言-miniblink浏览器模块(含源码)18-08-14

    早期miniblink还导出了CEF接口,不过现在已被废弃。miniblink有个小demo,从demo里可以看到,brackct这个基于cef的开源编辑器,已经顺利由miniblink跑起来了。现在electron的接口已做好,vscode跑起来了。更详细的...

    HttpClient以及获取页面内容应用

    org.apache.http.impl.client.HttpClients 与 org.apache.commons.httpclient.HttpClient目前后者已被废弃,apache已不再支持。 一般而言,使用HttpClient均需导入httpclient.jar与httpclient-core.jar2个包。 1.4...

    Oracle9i的init.ora参数中文说明

    说明: 确定查询是否获取表级的读取锁, 以防止在包含该查询的事务处理被提交之前更新任何对象读取。这种操作模式提供可重复的读取, 并确保在同一事务处理种对相同数据的两次查询看到的是相同的值。 值范围: TRUE | ...

    Berkeley DB

    Berkeley DB (DB)是一个高性能的,嵌入数据库编程库,和C语言,C++,Java,Perl,Python,PHP,Tcl以及其他很多语言都...Berkeley DB可以支持数千的并发线程同时操作数据库,支持最大256TB的数据,为一个很好用的KV系统

    易语言-miniblink浏览器模块(含源码)18-08-14

    早期miniblink还导出了CEF接口,不过现在已被废弃。 miniblink有个小demo,从demo里可以看到,brackct这个基于cef的开源编辑器,已经顺利由miniblink跑起来了。现在electron的接口已做好,vscode跑起来了。 更详细的...

    Tcl_TK编程权威指南pdf

    过时废弃的功能 cgct操作 输入焦点的高亮显示 编联 滚动条接日 pack info 焦点 send命令 按钮的内部补白 单选按钮的值 输入条组件 菜单 列表框 没有了geometry属性 文本组件 颜色属性 颜色分配与tk ...

    超完整FreeMarker中文教程,代码

    前言................................................................................................................................. 7 什么是FreeMarker? ................................2.4 方法 ..........

    FreeMarker 手册

    FreeMarker 手册 .......................................................................................................... 1 用于 FreeMarker 2.3.18 .......................................................

Global site tag (gtag.js) - Google Analytics