并发编程之 Thread 类过期方法和常用方法

前言

在 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 方法的作用是继续执行,可以说这两个方法是对应的,先挂起,然后继续执行。这两个动作是相反的。但是为什么不建议使用呢?原因就似乎 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。

死锁 状态 却是 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 源码:

join 方法

该方法注释写到:等待该线程直到死。。。。还真是痴情啊。说正经的的。该方法实际上时等待线程结束。说明意思呢?

假如你有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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容

  • 昨天与一个朋友去跟一个师弟聊工程,师弟比我晚一年毕业,是一家上市公司副总,相谈甚欢,他有一句话给我印象深刻:无...
    姑苏阿杰阅读 171评论 0 1
  • The Cold War Is Hot Again: Films in 2018 ① Geopolitical t...
    新心断点阅读 207评论 0 0
  • 生和死,是个大问题。 人人都怕死,死本来是世界上最难的事情。 近一年来,生意失利,我觉得自己得了抑郁。 万念俱灰,...
    睡眠科学化阅读 1,254评论 0 0
  • 买完牛奶回家,等电梯的时候发生了这样一幕。 一个爸爸带着自己3岁多的孩子等电梯,同时还有一位坐电动轮椅的中年男人,...
    智水与仁山阅读 289评论 1 4