前言

在 Java 刚诞生时,Thread 类就已经有了很多方法,但这些方法由于一些原因(有一些明显的bug或者设计不合理)有些已经废弃了,但是他们的方法名却是非常的好,真的是浪费。我们在进行并发必编程的时候一定要注意这些。

  1. 过期方法1----- stop 方法
  2. 过期方法2------suspend 方法和 resume 方法
  3. 常用方法1------线程中断方法 interrupt,isInterrupted,static interrupted
  4. 常用方法2------等待线程结束 join 方法
  5. 常用方法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 类过期方法和常用方法的更多相关文章

  1. Java并发编程之ThreadLocal类

    ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当 ...

  2. c++并发编程之thread::join()和thread::detach()

    thread::join(): 阻塞当前线程,直至 *this 所标识的线程完成其执行.*this 所标识的线程的完成同步于从 join() 的成功返回. 该方法简单暴力,主线程等待子进程期间什么都不 ...

  3. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  4. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  5. python并发编程之threading线程(一)

    进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...

  6. Python核心技术与实战——十七|Python并发编程之Futures

    不论是哪一种语言,并发编程都是一项非常重要的技巧.比如我们上一章用的爬虫,就被广泛用在工业的各个领域.我们每天在各个网站.App上获取的新闻信息,很大一部分都是通过并发编程版本的爬虫获得的. 正确并合 ...

  7. 并发编程之J.U.C的第二篇

    并发编程之J.U.C的第二篇 3.2 StampedLock 4. Semaphore Semaphore原理 5. CountdownLatch 6. CyclicBarrier 7.线程安全集合类 ...

  8. 并发编程之J.U.C的第一篇

    并发编程之J.U.C AQS 原理 ReentrantLock 原理 1. 非公平锁实现原理 2)可重入原理 3. 可打断原理 5) 条件变量实现原理 3. 读写锁 3.1 ReentrantRead ...

  9. 并发编程之:ThreadLocal

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 从前上一期[并发编程之:synchronized] 我们学到要保证在并发情况下对于共享资源的安全访问,就需要用到锁. 但是,加锁通常情况下会让运行效率 ...

随机推荐

  1. Android-HttpClient-Get与Post请求登录功能

    HttpClient 是org.apache.http.* 包中的: 第一种方式使用httpclient-*.jar (需要在网上去下载httpclient-*.jar包) 把httpclient-4 ...

  2. Notes for Apue —— chapter 4 Files and Directories(文件和目录)

    4.1 Introduction 4.2 stat, fstat, fstatat, and lstat Functions The lstat function is similar to stat ...

  3. 利用 LibWebP-NET 解码与编码 WebP 格式图片

    此文以后将会在我的新博客更新,有任何疑问可在我的新博文中提出 https://blog.clso.fun/posts/2019-03-02/vb-net-webp.html WebP 格式是谷歌开发并 ...

  4. 浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序

    本文主要基于向HTML页面引入JavaScript的几种方式,分析HTML中JavaScript脚本的执行顺序问题 1. 关于JavaScript脚本执行的阻塞性 JavaScript在浏览器中被解析 ...

  5. 2019/4/23 todolist

    近期的任务单子大概是这样吧 bjoi2019改完,写题解 hnoi2019改一些,写题解 找3道网络流写写 写一场agc,写题解 找2道简单计算几何写写

  6. RabbitMQ在mac上的安装

    1.官网下载rabbitmq-server-3.6.3, 地址http://www.rabbitmq.com/install-standalone-mac.html.2.tar -zxvf rabbi ...

  7. Spring Boot log4j实现把日志存入mongodb

    准备工作 1.自定义appender的实现 log4j提供的输出器实现自Appender接口,要自定义appender输出到MongoDB,只需要继承AppenderSkeleton类,并实现几个方法 ...

  8. VS2013 编辑器

    1. VS -> 本地Git -> Github 1. 右键单击解决方案,选择“将解决方案添加到源代码管理器”,选择Git 2. 切换到团队资源管理器([菜单]视图->团队资源管理器 ...

  9. spring + mybatis 存取clob

    存的时候会比较麻烦,需要使用select for update的方式更新数据,如果原来没有这一条数据,还需要先新增,新增的时候需要将clob字段存为oracle.sql.CLOB.empty_lob( ...

  10. Hadoop和Apache Spark的异同

    谈到大数据,相信大家对Hadoop和Apache Spark这两个名字并不陌生.但我们往往对它们的理解只是提留在字面上,并没有对它们进行深入的思考,下面不妨跟我一块看下它们究竟有什么异同. 1.解决问 ...