并发编程之 Thread 类过期方法和常用方法
前言
在 Java 刚诞生时,Thread 类就已经有了很多方法,但这些方法由于一些原因(有一些明显的bug或者设计不合理)有些已经废弃了,但是他们的方法名却是非常的好,真的是浪费。我们在进行并发必编程的时候一定要注意这些。
- 过期方法1----- stop 方法
- 过期方法2------suspend 方法和 resume 方法
- 常用方法1------线程中断方法 interrupt,isInterrupted,static interrupted
- 常用方法2------等待线程结束 join 方法
- 常用方法3------线程让出时间片 yield 方法
1. 过期方法1----- stop 方法
JDK 源码:
该方法被定义了 @Deprecated 注解,并在注释中说明了为什么废弃:
该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。
很官方对不对?还是用楼主的话来解释一下吧。最重要的原因就i是 stop 太粗鲁了,强行把执行到一半的线程终止,引起数据不一致。比如有些数据处理到一半,该方法就强行停止线程,导致数据不一致。
可使用一个条件判断来代替此功能,比如设置一个变量,如果这个变量是ture 则跳出循环,结束线程的执行。反正不要使用该方法就对了。
2. 过期方法2------suspend 方法和 resume 方法
JDK 源码:
这两个方法都被标注为过期,楼主解释一下为什么不能使用。
suspend 方法的作用是挂起方法,而 resume 方法的作用是继续执行,可以说这两个方法是对应的,先挂起,然后继续执行。这两个动作是相反的。但是为什么不建议使用呢?原因就似乎 suspend 方法在导致线程暂停的同时,并不会释放任何锁资源。此时,其他任何线程想要访问被他暂用的锁时,都会被牵连,导致无法正常运行。知道对应的 resume 方法被调用,被挂起的线程才能继续。但是,请注意,这里严格要求 resume 方法在 suspend 方法后面执行,如果 resume 方法意外的在suspend 方法之前执行了,就会导致死锁,该线程拥有不会恢复。
最坑的是,当产生死锁的时候,你肯定会使用 jps 命令和 jstack 命令去查看死锁。但是你会发现你根本找不到,因为这个线程的状态是 Rannable。你根本无法判断是哪个线程被挂起了,所以,该方法一定要废弃。
比如楼主写了一个例子:
package cn.think.in.java.two;
public class BadSuspend {
static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
public void run() {
synchronized (u) {
System.out.println("in " + getName());
// 暂停
Thread.currentThread().suspend();
}
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
// 此时 t1 已经暂停
t2.start();
// t1 恢复
t1.resume();
// t2 这时恢复,但是 t2在恢复之后进入了暂停,导致死锁。
// 除非使用 sleep 让 t2 先暂停就可以。
// Thread.sleep(100);
t2.resume();
t1.join();
t2.join();
}
}
该方法会发生死锁。然而我们在命令行中使用 jstack 命令查看时,会发现该线程状态是 Rannable。
因此在以后的并发编程一定不要使用该方法。
3. 常用方法1------线程中断方法 interrupt,isInterrupted,static interrupted
关于线程中断还有3个方法:
public void interrupt()
public boolean isInterrupted()
public static boolean interrupted()
public void interrupt() 作用:中断线程,也就是设置中断标记,注意,是设置标记,不会中断。
public boolean isInterrupted() 作用:判断线程是否中断
static boolean Thread interrupted 作用:判断是否中断,并清除当前中断状态。
我们解释解释这三个方法: 在 java 中,线程中断是一种重要的线程协作机制。可以用来代替 stop方法,严格来讲, 线程中断并不会使线程立即退出, 而是给线程发一个通知,告知目标线程,有人希望你退出了。而什么时候退出,完全由线程自己自行决定,避免了stop 的问题。但是该方法只是设置标记,所以需要自己判断状态然后跳出循环之类的结束线程运行。
那么我们怎么使用这三个方法进行并发编程呢?下面楼主写了一个例子:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (; ; ) {
}
});
t1.start();
Thread.sleep(2000);
// 不会起任何作用,所以需要判断他的中断位状态
t1.interrupt();
}
该测试方法在死循环了一个线程,然后启动 interrupt 方法,根本不会起任何作用,所以各位不要这样使用该方法。那么如何使用呢?示例代码如下:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (; ; ) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("interrupt");
break;
}
Thread.yield();
}
});
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
使用 isInterrupted 方法进行判断,如果返回 ture ,表示有中断标记,那么则 break 循环。结束运行。
还有一个需要注意的地方就是,如果线程在 sleep 或者 wait 状态,如果你调用 interrput 方法就会导致InterruptedException 异常,但是,抛出异常时会清除中断标记,因此,线程也就中断不了了,如果你想在异常后仍然中断线程,那么你需要在 catch 中 继续设置状态,也就是调用 interrupt 方法。我们来个例子看看:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (; ; ) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("interrupt");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.err.println("Interrupt When Sleep");
// 由于在 sleep 之间中断线程导致抛出异常,此时,他会清楚中断位,所以需要在这里重新设置中断位,下次循环则会直接判断中断标记,从而break。
Thread.currentThread().interrupt();
// 该方法会清除中断状态,导致上面的一行代码失效
// boolean isInterrupt = Thread.interrupted();
// System.out.println(isInterrupt);
}
Thread.yield();
}
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
运行结果:
interrupt
Interrupt When Sleep
该测试方法中,在线程中调用了 sleep 方法,并在 main 线程中调用了 interrupt 方法,因此导致该线程异常,但是,如果我们不在 catch 中重新设置中断位,该线程永远不会停止。这个时需要注意的。
还有一个静态方法,Thread.interrupted(),其实我们上面的例子也测试了,该方法会返回线程是否中断,并且会清除状态,使用的时候需要注意。
4. 常用方法2------等待线程结束 join 方法
JDK 源码:
该方法注释写到:等待该线程直到死。。。。还真是痴情啊。说正经的的。该方法实际上时等待线程结束。说明意思呢?
假如你有2个线程,A线程在算 1+1 ,而B线程需要 A线程算出的结果,那么B线程就需要等待A线程,那么这时候,B线程就需要调用 A线程的 join 方法,调用该方法后, B线程就会被挂起,直到A线程死亡,B线程才会被唤醒。实际上,如果看 join 的源码,会发现内部调用了A线程的 wait 方法。也就是说,B 线程 wait 在了 A 线程上。A 线程执行完毕会调用 notifyAll 方法,唤醒B线程。
我们写个demo:
package cn.think.in.java.two;
public class JoinTest {
static int i;
public static void main(String[] args) throws InterruptedException {
AddThread addThread = new AddThread();
addThread.start();
// 主函数等待 addThread
// join 的本质是调用了 wait方法,让调用线程 wait 在当前线程对象实例上。也就是main线程 wait 在 addThread 线程实例上。
// 当 addThread 执行结束后,会调用 notifyAll 方法,注意,不要再程序中调用线程的 wait 或者 notify 方法,
// 可能会影响系统API 的工作。
addThread.join();// 重载方法 join(long) 如果达到给定的毫秒数,则不等了
System.out.println(i);
}
static class AddThread extends Thread {
public void run() {
for (; i < 10000000; i++) {
}
}
}
}
该测试方法运行了一个对变量 i 自增运算的线程,并且主线程在等待 addThread 线程执行完才打印 i 的结果。如果不使用 join , 那么 ,打印 i 的值永远会小于10000。
而 join 的内部实现,我们刚刚说了,使用 wait 方法,我们看看该方法:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
该方法时同步的,同时内部调用了自身的 wait 方法。注意:我们最好不要调用线程的 wait 方法和 notify 方法,可能会导致系统 api 出现问题。
5. 常用方法3------线程让出时间片 yield 方法
这个方法就比较简单了。这是一个静态方法,yield 谦让出CPU时间片;
yieid 会让出时间片,但是是随机的。如果你觉得一个线程不是很重要,那就可以适当的调用该方法,给予其他线程更多的机会。
拾遗
sleep 方法
虽然用的很多,但有必要说一下,该方法不会释放当前线程的锁。面试中常有该问题,wait 方法和 sleep 方法有什么不同,wait 方法会释放锁,sleep 方法不会释放锁。
holdsLock 方法:
仅当当前线程在指定的对象上保持监视器锁时,才返回 true。该方法旨在使程序能够断言当前线程已经保持一个指定的锁。
参数:
obj - 用于测试锁所属权的对象
返回:
如果当前线程在指定的对象上保持监视器锁,则返回 true。
setContextClassLoader 方法:
设置该线程的上下文 ClassLoader。上下文 ClassLoader 可以在创建线程设置,并允许创建者在加载类和资源时向该线程中运行的代码提供适当的类加载器。
首先,如果有安全管理器,则通过 RuntimePermission("setContextClassLoader") 权限调用其 checkPermission`方法,查看是否可以设置上下文 ClassLoader。该方法在违反 JDK 默认的类加载模型时能起到很大作用。
参数:
该线程的上下文 ClassLoader
抛出:
SecurityException - 如果当前线程无法设置上下文 ClassLoader。
并发编程之 Thread 类过期方法和常用方法的更多相关文章
- Java并发编程之ThreadLocal类
ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当 ...
- c++并发编程之thread::join()和thread::detach()
thread::join(): 阻塞当前线程,直至 *this 所标识的线程完成其执行.*this 所标识的线程的完成同步于从 join() 的成功返回. 该方法简单暴力,主线程等待子进程期间什么都不 ...
- 并发编程之 Exchanger 源码分析
前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
- python并发编程之threading线程(一)
进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...
- Python核心技术与实战——十七|Python并发编程之Futures
不论是哪一种语言,并发编程都是一项非常重要的技巧.比如我们上一章用的爬虫,就被广泛用在工业的各个领域.我们每天在各个网站.App上获取的新闻信息,很大一部分都是通过并发编程版本的爬虫获得的. 正确并合 ...
- 并发编程之J.U.C的第二篇
并发编程之J.U.C的第二篇 3.2 StampedLock 4. Semaphore Semaphore原理 5. CountdownLatch 6. CyclicBarrier 7.线程安全集合类 ...
- 并发编程之J.U.C的第一篇
并发编程之J.U.C AQS 原理 ReentrantLock 原理 1. 非公平锁实现原理 2)可重入原理 3. 可打断原理 5) 条件变量实现原理 3. 读写锁 3.1 ReentrantRead ...
- 并发编程之:ThreadLocal
大家好,我是小黑,一个在互联网苟且偷生的农民工. 从前上一期[并发编程之:synchronized] 我们学到要保证在并发情况下对于共享资源的安全访问,就需要用到锁. 但是,加锁通常情况下会让运行效率 ...
随机推荐
- Delphi 的多线程使用已经很简单了
先看一个非多线程的例子, 代码执行时不能进行其它操作(譬如拖动窗体): {自定义方法: 在窗体上绘制...} procedure MyMethod; var i: Integer; begin ...
- Delphi IDHTTP控件:GET/POST 请求
Delphi IDHTTP控件:GET/POST 请求 最近一直在使用IDHTTP,下面是一些关于 GET.POST 请求基本使用方法的代码 一.GET 请求 1 procedure GetDem ...
- [转载]DevOps发展的四个重要阶段
DevOps是敏捷开发的延续,它将敏捷的精神延伸至IT运营(IT Operation)阶段.敏捷开发的主要目的是响应变化,快速交付价值.以2001年的敏捷宣言发布这个里程碑为起点,开始几年内企业主要在 ...
- unity 人工智能AI,装备解锁临时笔记
A*算法的一种改进设想:1.如何让角色到达目标点的过程中更加平滑:获取一串到达目标点的网格串之后,就实时用带形状的物理射线检测能否直接到达下一个目标点的再下一个目标点,如果能到达,那么直接朝该方向运动 ...
- Git入门--创建版本库,关联远程库,从远程库下载
1.(先进入项目文件夹)通过命令 git init 把这个目录变成git可以管理的仓库 git init 2.把文件添加到版本库中,使用命令 git add .添加到暂存区里面去,不要忘记后面的小数点 ...
- 201621123018《Java程序设计》第8周学习报告
1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains源代码 contanis方 ...
- Python 爬虫(二十五) Cookie的处理--cookielib库的使用
Python中cookielib库(python3中为http.cookiejar)为存储和管理cookie提供客户端支持. 该模块主要功能是提供可存储cookie的对象.使用此模块捕获cookie并 ...
- mysql查询语句分析 explain/desc用法
explain或desc显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. explain 数据表 或 desc 数据表 显示数据表各字段含义 ...
- 关于UUID
UUID是通用唯一识别码的缩写,其目的,是让分布式系统中的所有元素,都能有唯一的辨识信息. UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的. 在做后台管理的时候,经常会碰 ...
- Nginx安装使用及与tomcat实现负载均衡
1. 背景 基于nginx强大的功能,实现一种负载均衡,或是不停机更新程序等.nginx相比大家基本上都知道是什么来头了,具体的文章大家可以去搜索相关文章学习阅读,或是可以查看Nginx中文文档和Ng ...