控制线程

java 的线程支持提供了一些便捷的 工具方法,通过这些便捷的工具方法可以很好的控制线程的执行。

前情提要

线程状态有:新建(New)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Dead)

  1. 线程的新建和启动都是在别的线程中操作的。
  2. 进入 运行状态取决于 线程调度器的调度。
  3. 进入阻塞状态

    调用 join()也可以使当前线程阻塞
  4. 进入Dead 状态呢

    stop 方法已经被标记为过时了

    interrupt() 方法也可以使线程结束

一、阻塞

1. join()

Thread提供了让一个线程等待另一个线程完成的方法——join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。

join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。

join(),虽然看起来是操作别的线程,但本质是操作自己,让自己停下来等待。

package com.rrz;public class MyThread extends Thread {public MyThread(String name) {super(name);}public void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);}}public static void main(String[] args) throws InterruptedException {new MyThread("子线程").start();for (int i = 0; i < 100; i++) {if (i == 10) {MyThread myThread = new MyThread("被 join 线程");myThread.start();myThread.join();}System.out.println(Thread.currentThread().getName() +"-"+ i);}}
}

当 i == 10 时,被 join 线程 和 子线程 一起并发运行

直到 被 join 线程 结束,main 线程才有继续开始运行

例子展示的只是 join 了一个线程,并没有什么实际意义,但如果 join 几个线程就是很美妙的了。这将体现将一个大问题分解为几个小问题解决的思维。

join()方法有如下三种重载形式。

  • join():等待被join的线程执行完成。
  • join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待。
  • join(long millis, int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫微秒。

提示:
通常很少使用第三种形式,原因有两个:程序对时间的精度无须精确到毫微秒;计算机硬件、操作系统本身也无法精确到毫微秒。

2. 线程睡眠 sleep

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。sleep()方法有两种重载形式。

  • static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
  • static void sleep(long millis, int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。

当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。

此外,Thread还提供了一个与sleep()方法有点相似的yield()静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。yield()只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。

实际上,当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。

关于sleep()方法和yield()方法的区别如下。

  • sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会。
  • sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程被yield()方法暂停之后,立即再次获得处理器资源被执行。
  • sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。
  • sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。

3. 暂停线程

这儿是暂停,不是中断
暂停意味这次线程还可以恢复运行。

使用 suspend 方法暂停线程,使用 resume 恢复线程。

package com.rrz;public class MyThread extends Thread {private int n = 0;public int getN() {return n;}public void setN(int n) {this.n = n;}public void run() {while(true){n++;}}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒,让 myThread 运行sleep(1);// 暂停线程myThread.suspend();System.out.println("第一次暂停" + myThread.getN());sleep(1);System.out.println("过了一毫秒后" + myThread.getN());myThread.resume();sleep(1);myThread.suspend();System.out.println("线程恢复1毫秒后暂停" + myThread.getN());// 等待线程myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}


线程的确暂停了,而且也可以恢复运行。

但是在使用 suspend 和 resume 时,如果使用不当,极其容易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。

package com.rrz;public class User {synchronized public void printUser() {System.out.println("begin");if (Thread.currentThread().getName().equals("a")) {System.out.println("a 线程 调用了 suspend");Thread.currentThread().suspend();}System.out.println("end");}
}
package com.rrz;import org.omg.PortableServer.THREAD_POLICY_ID;public class MyThread extends Thread {public static void main(String[] args) throws InterruptedException {try{User user = new User();Thread myThread1 = new Thread(user::printUser);myThread1.setName("a");myThread1.start();sleep(1);// 此时 myThread1 已经调用 suspendThread myThread2 = new Thread(() -> {System.out.println("线程b启动了");user.printUser();System.out.println("线程b结束了");});myThread2.start();// 等待线程myThread1.join();myThread2.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}


线程a 暂停了,但是线程b 同样没有动静,因为a 拿着锁对象不释放。

踩坑补充

public class MyThread extends Thread {public static void main(String[] args) throws InterruptedException {try{Thread myThread2 = new Thread(() -> {int i = 0;while (true) {i++;}});myThread2.start();sleep(1);myThread2.suspend();System.out.println("main 打印");// 等待线程myThread2.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}

打印出了 main 打印

package com.rrz;import org.omg.PortableServer.THREAD_POLICY_ID;public class MyThread extends Thread {public static void main(String[] args) throws InterruptedException {try{Thread myThread2 = new Thread(() -> {int i = 0;while (true) {System.out.println(i++);}});myThread2.start();sleep(1);myThread2.suspend();System.out.println("main 打印");// 等待线程myThread2.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}

没有“main 打印”

原因是:

println 也是同步锁的。

同时 suspend 和 resume 也容易出现线程的短暂暂停而导致数据不同步的情况,和 stop 类似。不过 stop 是停止导致,这个是暂停导致。

二、中断线程

package com.rrz;public class MyThread extends Thread {private int n = 0;public void run() {for (int i=0; i < 3000; i++) {n++;System.out.println("hello:" + n);}}public static void main(String[] args) throws InterruptedException {MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒,让线程先飞一会sleep(1);// 发现1毫秒内  线程在 hello:100 之内停住了// 主要用来测试,之后注释myThread.suspend();// 中断线程// 之后需要解开注释//myThread.interrupt();// 等待线程myThread.join();System.out.println("end");}
}


之后注释 myThread.suspend(); 打开myThread.interrupt();
运行:如果在 100 内中断了,说明中断生效。如果运行结束,那就是中断未生效。
结果:

jdk Thread.java 类中提供了两种方法

  • interrupted(): 测试当前线程是否中断
  • isInterrupted():测试线程是否已经中断。
public static boolean interrupted() {return currentThread().isInterrupted(true);}
private native boolean isInterrupted(boolean ClearInterrupted);

I、 interrupted()

package com.rrz;import org.omg.CORBA.INTERNAL;public class MyThread extends Thread {private int n = 0;public void run() {//while (!isInterrupted()) {//    n++;System.out.println("hello:" + n);//}for (int i=0; i < 100; i++) {n++;System.out.println("hello:" + n);}}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒sleep(1);// 中断线程myThread.interrupt();System.out.println("是否停止1?" + myThread.interrupted());System.out.println("是否停止2?" + myThread.interrupted());// 等待线程中断myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}System.out.println("end");}
}


说明,interrupted 是测试当前线程是否中断,而当前线程是 main。而且他的代码中也标明了,currentThread().isInterrupted(true)

public static void main(String[] args) throws InterruptedException {Thread.currentThread().interrupt();System.out.println("是否停止1?" + Thread.interrupted());System.out.println("是否停止2?" + Thread.interrupted());System.out.println("end");}


从结果看,interrupted 的确判断出当前线程是否是停止状态,但是 为什么第二个 布尔值是 false 呢?

官方api: 测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用改方法,则第二次返回 false。(在第一次调用已清除了其中断状态之后,且第二次调用检验前,当前线程再次中断的情况除外。)

interrupted () 方法具有清除状态的功能。

II、isInterrupted()

public class MyThread extends Thread {private int n = 0;public void run() {//while (!isInterrupted()) {//    n++;System.out.println("hello:" + n);//}for (int i=0; i < 100; i++) {n++;System.out.println("hello:" + n);}}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒sleep(1);// 中断线程myThread.interrupt();System.out.println("是否停止1?" + myThread.isInterrupted());System.out.println("是否停止2?" + myThread.isInterrupted());// 等待线程中断myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}


从结果看出, isInterrupted 并未清除状态。

结论:
1. interrupted():测试当前线程是否是中断状态,执行后具有将状态标志清除为 false 的功能。
2. isInterrupted ():测试线程 Thread 对象是否已经是中断状态,但是不清楚状态标志。

III 能停止的线程

public class MyThread extends Thread {private int n = 0;public void run() {for (int i=0; i < 100; i++) {n++;System.out.println("hello:" + n);if (Thread.interrupted()) {System.out.println("我的状态是中断状态,之后我会清除自己的中断状态");}}System.out.println("我被输出的话,表示程序并没有中断");}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒sleep(1);// 中断线程myThread.interrupt();// 等待线程中断myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}


1. 使用异常法让其中断

package com.rrz;public class MyThread extends Thread {private int n = 0;public void run() {try{for (int i=0; i < 100; i++) {n++;System.out.println("hello:" + n);if (Thread.interrupted()) {System.out.println("我的状态是中断状态,之后我会清除自己的中断状态");throw new InterruptedException();}}System.out.println("我被输出的话,表示程序并没有中断");}catch (InterruptedException e) {System.out.println("MyThread 对象中捕捉到的异常");}}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒sleep(1);// 中断线程myThread.interrupt();// 等待线程中断myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}

2. 在沉睡中停止

package com.rrz;public class MyThread extends Thread {private int n = 0;public void run() {try{for (int i=0; i < 100; i++) {n++;System.out.println("hello:" + n);Thread.sleep(2);}System.out.println("我被输出的话,表示程序并没有中断");}catch (InterruptedException e) {System.out.println("MyThread 对象--“睡眠中--捕捉到的异常");}}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒sleep(1);// 中断线程myThread.interrupt();// 等待线程中断myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}

在 Thread.sleep(2); 已经抛出了异常,后面的没机会执行

结论: 如果在 sleep 状态下停止一个线程,会抛出 InterruptedException 异常。

之前是先 sleep ,后 interrupt
现在先 interrupt ,后 sleep

package com.rrz;public class MyThread extends Thread {private int n = 0;public void run() {try{for (int i=0; i < 100; i++) {n++;System.out.println("hello:" + n);}Thread.sleep(2);System.out.println("我被输出的话,表示程序并没有中断");}catch (InterruptedException e) {System.out.println("MyThread 对象--“睡眠中--捕捉到的异常");}}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒sleep(1);// 中断线程myThread.interrupt();// 等待线程中断myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}

结论: 我们可以利用 sleep 来制造异常,并可控制中断的时机。

当然,上述代码用 isInterrupted 也不错。

注意:
interrupt()方法仅仅向t线程发出了“中断请求”,而 myThread线程 的while循环会检测isInterrupted(),所以上述代码能正确响应interrupt()请求,使得自身立刻结束运行run()方法。

3. 暴力停止

使用 stop()方法,已经被弃用

因为如果强制让线程停止的话则可能有一些请理性的工作得不到完成。另外一个情况就是对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的问题。

public class User {private String username = "a";private String password = "aa";public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}synchronized public void changeUser(String username, String password) {this.username = username;try {sleep(20);} catch (InterruptedException e) {e.printStackTrace();}this.password = password;}
}
public class MyThread extends Thread {private User user;public MyThread(User user) {super();this.user = user;}public void run() {user.changeUser("b", "bb");}public static void main(String[] args) throws InterruptedException {try{User user = new User();MyThread myThread = new MyThread(user);myThread.start();// 暂停1毫秒sleep(10);// 中断线程myThread.stop();// 等待线程中断myThread.join();System.out.println("name:" + user.getUsername() +","+ "password:" +user.getPassword());}catch (InterruptedException e) {System.out.println("main catch");}}
}

4. return 停止

使用 interrupt 和 return 结合

package com.rrz;public class MyThread extends Thread {private int n = 0;public void run() {for (int i=0; i < 100; i++) {n++;System.out.println("hello:" + n);if (isInterrupted()) {System.out.println("停止了");return;}}System.out.println("我被输出的话,表示程序并没有中断");}public static void main(String[] args) throws InterruptedException {try{MyThread myThread = new MyThread();myThread.start();// 暂停1毫秒sleep(1);// 中断线程myThread.interrupt();// 等待线程中断myThread.join();}catch (InterruptedException e) {System.out.println("main catch");}}
}


不过还是建议使用抛异常的方式来实现线程的停止,因为 catch 快中还可以将异常向上抛出,使线程停止的时间得以传播。

三、线程优先级

每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。

每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。

Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的如下三个静态常量。

  • MAX_PRIORITY:其值是10。
  • MIN_PRIORITY:其值是1。
  • NORM_PRIORITY:其值是5。
public class MyThread extends Thread {public MyThread(String name) {super(name);}public void run() {for (int i = 0; i < 50; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);}}public static void main(String[] args) throws InterruptedException {// 给主线程设置优先级Thread.currentThread().setPriority(6);for (int i = 0; i < 50; i++) {if (i == 10) {MyThread low = new MyThread("低级");low.start();System.out.println("低级线程创建之初的优先级" + low.getPriority());low.setPriority(1);}if (i == 20) {MyThread high = new MyThread("高级");high.start();System.out.println("高级线程创建之初的优先级" + high.getPriority());high.setPriority(9);}}}
}


高级执行的机会多,所以先执行完

尽量使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设置优先级,这样才可以保证程序具有最好的可移植性。

四、后台线程(守护线程)

有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。

后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。

调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。

package com.rrz;public class MyThread extends Thread {public MyThread(String name) {super(name);}public void run() {for (int i = 0; i < 1000; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);}}public static void main(String[] args) throws InterruptedException {MyThread myThread = new MyThread("守护线程");myThread.setDaemon(true);myThread.start();for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() +"-"+ i);}}
}



本来该线程应该执行到i等于999时才会结束,但运行程序时不难发现该后台线程无法运行到999,因为当主线程也就是程序中唯一的前台线程运行结束后,JVM会主动退出,因而后台线程也就被结束了。

而且,守护线程并没有一下就结束,还是打印了几条的。

前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

注意:
前台线程死亡后,JVM会通知后台线程死亡,但从它接收指令到做出响应,需要一定时间。而且要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说,setDaemon(true)必须在start()方法之前调用,否则会引发IllegalThreadStateException异常。

dxc 4.0 控制线程相关推荐

  1. java condition详解_Java使用Condition控制线程通信的方法实例详解

    Java使用Condition控制线程通信的方法实例详解 发布于 2020-4-20| 复制链接 摘记: 本文实例讲述了Java使用Condition控制线程通信的方法.分享给大家供大家参考,具体如下 ...

  2. C# 多线程 线程池(ThreadPool) 2 如何控制线程池?

    线程池启动了,但是没有方法去控制线程池,如果子线程出现了问题,难道线程池就死了吗? 我们可以设置线程池的线程数量,进行加入任务,线程池会自动分配并且合理的执行,但是控制不了又有啥意思呢. 线程池里线程 ...

  3. 智能家居 (8) ——智能家居项目整合(网络控制线程、语音控制线程,火灾报警线程)

    目录 mainPro.c(主函数) 指令工厂 inputCommand.h voiceControl.c(语音控制) socketControl.c(网络线程) 控制工厂 contrlEquipmen ...

  4. python多线程_python多线程:控制线程数量

    背景 前段时间学习了python的多线程爬虫,当时爬取一个图片网站,开启多线程后,并没有限制线程的数量,也就是说,如果下载1000张图片,会一次性开启1000个子线程同时进行下载 现在希望控制线程数量 ...

  5. Java中怎么控制线程訪问资源的数量

    在API中是这样来描写叙述Semaphore 的 Semaphore 通经常使用于限制能够訪问某些资源(物理或逻辑的)的线程数目. 一个计数信号量.从概念上讲,信号量维护了一个许可集.如有必要,在许可 ...

  6. java 线程 condition_Java编程中实现Condition控制线程通信

    java中控制线程通信的方法 1.传统的方式:利用synchronized关键字来保证同步,结合wait(),notify(),notifyall()控制线程通信.不灵活. 2.利用condition ...

  7. java queue通信_Java -- 使用阻塞队列(BlockingQueue)控制线程通信

    BlockingQueeu接口是Queue的子接口,但是它的主要作用并不是作为容器,而是作为线程同步的工具. 特征: 当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程 ...

  8. Java发令枪ConcurrentExecutor之控制线程数,并发数

    前言 前段时候学习单例模式的时候,有用到多线程并发数去测试单例模式的线程安全.但是当时时间比较紧没有进行记录,今天特地记录一下. 1.先看代码 import java.util.concurrent. ...

  9. 智能家居 (5) ——智能家居项目整合(语音控制线程,网络控制线程、烟雾报警线程)

    目录 一.主函数 mianPro.c 二.指令工厂 voiceControl.c socketControl.c inputCommand.h 三.设备工厂 smokeAlarm.c buzzer.c ...

最新文章

  1. C#从剪贴板中获取数据
  2. python wing 免费下载安装
  3. sysfs cannot create duplicate filename问题
  4. 07-CA/TA编程:rsakey demo
  5. python enumerate()函数
  6. mybaits中resultMap实现多对多查询映射
  7. Telnet发邮件过程
  8. 对棋盘完美覆盖问题证明过程的质疑及其解决
  9. 转移 AD Time Server
  10. python转cython_Cython的简单使用
  11. Linux 2.6内核Makefile浅析
  12. Windows Mac 光盘刻录软件
  13. 车路协同场景身份认证及 V2X 通信安全保障
  14. Unity Spine SkeletonGraphic 动画重复播放 过度残影透明渐变Bug 解决方案
  15. 接口测试实战项目02:根据接口文档测试
  16. 中国生物降解塑料行业“十四五”发展规划及未来前景展望报告2021年版
  17. 央视气象女主播王蓝一揭秘天气预报潜规则
  18. codeblocks(自带编译器的zip版)下载
  19. 在线vr模型展示-3D可视化展示解决方案
  20. IOS APP更新问题

热门文章

  1. FAQ02【Hive】:Hive连接后出现一堆乱七八糟的日志
  2. 【论文阅读笔记】Noise2Noise: Learning Image Restoration without Clean Data
  3. 垂杨柳中学2021年高考成绩查询时间,2021年中考成绩
  4. oralce字符串函数
  5. 机械键盘Windows键失灵,解决办法
  6. [NEXT] 时间管理实践
  7. Java6、7章总结复习
  8. VR社交应用Rec Room再获1.45亿美元融资,用户突破3700万
  9. 供应链管理与企业边界—基于超边际的分析框架 (zt)
  10. 慢性病概念——>分类——>数据集