控制线程

摘要

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

1. join线程控制,让一个线程等待另一个线程完成的方法

2. 后台线程,又称为守护线程或精灵线程。它的任务是为其他的线程提供服务,如果所有的前台线程都死亡,后台线程会自动死亡

3. 线程睡眠sleep,让当前正在执行的线程暂停一段时,并进入阻塞状态

4. 线程让步yield,让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态

一、join线程

Thread提供了让一个线程等待另一个线程完成的方法join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。

  1. package test;
  2.  
  3. public class JoinThread extends Thread {
  4.    // 提供一个有参数的构造器,用于设置该线程的名字
  5.    public JoinThread(String name) {
  6.       super(name);
  7.    }
  8.  
  9.    // 重写run方法,定义线程执行体
  10.    public void run() {
  11.       for (int i = 0; i < 100; i++) {
  12.          System.out.println(getName() + "" + i);
  13.       }
  14.    }
  15.  
  16.    public static void main(String[] args) throws Exception {
  17.       // 启动子线程
  18.       new JoinThread("新线程").start();
  19.       for (int i = 0; i < 100; i++) {
  20.          if (i == 20) {
  21.             JoinThread jt = new JoinThread("被Join的线程");
  22.             jt.start();
  23.             // main线程调用了jt线程的join()方法,main线程
  24.             // 必须等jt执行结束才会向下执行
  25.             jt.join();
  26.          }
  27.          System.out.println(Thread.currentThread().getName() + "" + i);
  28.       }
  29.    }
  30. }

运行结果:

main 0

main 1

main 2

main 3

新线程 0

main 4

新线程 1

main 5

新线程 2

main 6

新线程 3

main 7

新线程 4

main 8

新线程 5

main 9

新线程 6

新线程 7

新线程 8

main 10

新线程 9

main 11

新线程 10

main 12

新线程 11

main 13

新线程 12

main 14

新线程 13

main 15

新线程 14

main 16

…………

main 18

新线程 17

main 19

新线程 18

新线程 19

…………

新线程 99

被Join的线程 0

…………

被Join的线程 99

main 20

上面程序中一共有3个线程,主方法开始时就启动了名为"新线程"的子线程,该子线程将会和main线程并发执行。当主线程的循环变量i等于20时启动了名为"被Join的线程"的线程,该线程不会和main线程并发执行。main线程必须等该线程执行结束后才可以向下执行。在名为"被Join的线程"的线程执行时,实际上只有2个子线程并发执行,而主线程处于等待状态。运行上面程序。从上面的运行结果可以看出,主线程执行到i=20时启动,并join了名为"被Join的线程"的线程,所以主线程将一直处于阻塞状态,直到名为"被Join的线程"的线程执行完成。

二、后台线程

有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为后台线程(Daemon Thread),又称为守护线程或精灵线程。JVM的垃圾回收线程就是典型的后台线程。后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。

调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。下面程序将执行线程设置成后台线程,可以看到当所有的前台线程死亡时,后台线程随之死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。

  1. public class DaemonThread extends Thread {
  2.    // 定义后台线程的线程执行体与普通线程没有任何区别
  3.    public void run() {
  4.       for (int i = 0; i < 1000; i++) {
  5.          System.out.println(getName() + "" + i);
  6.       }
  7.    }
  8.  
  9.    public static void main(String[] args) {
  10.       DaemonThread t = new DaemonThread();
  11.       // 将此线程设置成后台线程
  12.       t.setDaemon(true);
  13.       // 启动后台线程
  14.       t.start();
  15.       for (int i = 0; i < 10; i++) {
  16.          System.out.println(Thread.currentThread().getName() + "" + i);
  17.       }
  18.       // -----程序执行到此处,前台线程(main线程)结束------
  19.       // 后台线程也应该随之结束
  20.    }
  21. }

运行结果:

main 0

Thread-0 0

main 1

Thread-0 1

main 2

Thread-0 2

main 3

Thread-0 3

main 4

Thread-0 4

main 5

Thread-0 5

main 6

Thread-0 6

main 7

Thread-0 7

main 8

Thread-0 8

main 9

Thread-0 9

Thread-0 10

Thread-0 11

Thread-0 12

Thread-0 13

Thread-0 14

Thread-0 15

Thread-0 16

Thread-0 17

Thread-0 18

Thread-0 19

上面程序中的t线程设置成后台线程,然后启动该线程,本来该线程应该执行到i等于999时才会结束,但运行程序时不难发现该后台线程无法运行到999,因为当主线程也就是程序中唯一的前台线程运行结束后,JVM会主动退出,因而后台线程也就被结束了。Thread类还提供了一个isDaemon0方法,用于判断指定线程是否为后台线程

从上面程序可以看出,主线程默认是前台线程, t线程默认也是前台线程。并不是所有的线程默认都是前台线程,有些线程默认就是后台线程——前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程

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

三、线程睡眠---sleep

如果需要让当前正在执行的线程暂停一段时,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。

下面程序调用sleep()方法来暂停主线程的执行,因为该程序只有一个主线程,当主线程进入睡眠后,系统没有可执行的线程,所以可以看到程序在sleep()方法处暂停

public class SleepTest {

public static void main(String[] args) throws Exception {

for (int i = 0; i < 10; i++) {

System.out.println("当前时间: " + new Date());

// 调用sleep方法让当前线程暂停1s。

Thread.sleep(1000);

}

}

}

上面程序中sleep()方法将当前执行的线程暂停1秒,运行上面程序,看到程序依次输出10条字符串,输出2条字符串之间的时间间隔为1秒。

四、线程让步---yield()

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

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

  1. public class YieldTest extends Thread {
  2.    public YieldTest(String name) {
  3.       super(name);
  4.    }
  5.  
  6.    // 定义run方法作为线程执行体
  7.    public void run() {
  8.       for (int i = 0; i < 50; i++) {
  9.          System.out.println(getName() + "" + i);
  10.          时,使用yield方法让当前线程让步
  11.          if (i == 20) {
  12.             Thread.yield();
  13.          }
  14.       }
  15.    }
  16.  
  17.    public static void main(String[] args) throws Exception {
  18.       // 启动两条并发线程
  19.       YieldTest yt1 = new YieldTest("高级");
  20.       // 将ty1线程设置成最高优先级
  21.       // yt1.setPriority(Thread.MAX_PRIORITY);
  22.       yt1.start();
  23.       YieldTest yt2 = new YieldTest("低级");
  24.       // 将yt2线程设置成最低优先级
  25.       // yt2.setPriority(Thread.MIN_PRIORITY);
  26.       yt2.start();
  27.    }
  28. }

运行结果:

高级 0

低级 0

高级 1

低级 1

高级 2

低级 2

高级 3

低级 3

高级 4

低级 4

高级 5

低级 5

高级 6

低级 6

高级 7

低级 7

高级 8

低级 8

高级 9

低级 9

高级 10

低级 10

高级 11

低级 11

高级 12

低级 12

高级 13

低级 13

高级 14

低级 14

高级 15

低级 15

高级 16

低级 16

高级 17

低级 17

高级 18

低级 18

高级 19

低级 19

高级 20

低级 20

高级 21

低级 21

高级 22

低级 22

高级 23

低级 23

高级 24

低级 24

高级 25

……

低级 48

高级 49

低级 49

上面程序中调用的yield()静态方法让当前正在执行的线程暂停,让系统线程调度器重新调度。由于程序中第21行、第25行代码处于注释状态——即两个线程的优先级完全一样,所以当一个线程使用yield()方法暂停后,另一个线程就会开始执行。如果将YieldTest.java程序中两行代码的注释取消,也就是为两个线程分别设置不同的优先级,则程序的运行结果示。

高级 0

高级 1

高级 2

高级 3

高级 4

高级 5

高级 6

高级 7

高级 8

高级 9

高级 10

高级 11

高级 12

高级 13

高级 14

高级 15

高级 16

高级 17

高级 18

高级 19

高级 20

高级 21

高级 22

高级 23

高级 24

高级 25

高级 26

高级 27

高级 28

高级 29

高级 30

高级 31

高级 32

高级 33

高级 34

高级 35

高级 36

高级 37

高级 38

高级 39

高级 40

高级 41

高级 42

高级 43

高级 44

高级 45

高级 46

高级 47

高级 48

高级 49

低级 0

低级 1

………

低级 48

低级 49

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

1. sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会

2. sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态:而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行

3. sleep()方法声明抛出了InterruptcdException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常

4. sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【Sunddenly】。

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利

Java多线程学习(四)---控制线程的更多相关文章

  1. Java多线程系列四——控制线程执行顺序

    假设有线程1/线程2/线程3,线程3必须在线程1/线程2执行完成之后开始执行,有两种方式可实现 Thread类的join方法:使宿主线程阻塞指定时间或者直到寄生线程执行完毕 CountDownLatc ...

  2. JAVA多线程学习四 - CAS(乐观锁)

    本文讲解CAS机制,主要是因为最近准备面试题,发现这个问题在面试中出现的频率非常的高,因此把自己学习过程中的一些理解记录下来,希望能对大家也有帮助. 什么是悲观锁.乐观锁?在java语言里,总有一些名 ...

  3. java多线程学习-同步之线程通信

    这个示例是网上烂大街的,子线程循环100次,主线程循环50次,但是我试了很多次,而且从网上找了很多示例,其实多运行几次,看输出结果并不正确.不知道是我转牛角尖了,还是怎么了.也没有大神问,好痛苦.现在 ...

  4. Java多线程学习笔记之一线程基础

    1.进程与线程 1.1 进程:是正在运行中的程序的实例,一个运行中idea就是一个进程.进程有它自己的地址空间,一般情况下,包括文本区域(text region).数据区域(data region)和 ...

  5. JAVA多线程学习六-守护线程

    java中的守护程序线程是一个服务提供程序线程,它为用户线程提供服务. 它的生命依赖于用户线程,即当所有用户线程都死掉时,JVM会自动终止该线程. 有许多java守护程序线程自动运行,例如 gc,fi ...

  6. JAVA多线程学习五:线程范围内共享变量&ThreadLocal

    一.概念 可以将每个线程用到的数据与对应的线程号存放到一个map集合中,使用数据时从这个集合中根据线程号获取对应线程的数据,就可以实现线程范围内共享相同的变量. 二.代码 Runnable中的run( ...

  7. Java多线程学习(四)等待/通知(wait/notify)机制

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  8. “全栈2019”Java多线程第四章:设置和获取线程名称

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  9. Java多线程学习(五)线程间通信知识点补充

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

随机推荐

  1. 浅析C/C++中的switch/case陷阱

    浅析C/C++中的switch/case陷阱 先看下面一段代码: 文件main.cpp #include<iostream> using namespace std; int main(i ...

  2. matlab练习程序(局部加权线性回归)

    通常我们使用的最小二乘都需要预先设定一个模型,然后通过最小二乘方法解出模型的系数. 而大多数情况是我们是不知道这个模型的,比如这篇博客中z=ax^2+by^2+cxy+dx+ey+f 这样的模型. 局 ...

  3. NAT穿越(一) NAT类型

    NAT分为四种类型: (1)完全透明NAT(Full Cone NAT): 从内部主机  (IN IP ipa) +端口(IN PORT porta) 发送的数据映射为  IP(OUT IP IPA) ...

  4. spring4笔记----报错publicid systemid之间要有空格的解决方法

    <?xml version="1.0" encoding="GBK"?> <beans xmlns:xsi="http://www. ...

  5. C#-循环语句(六)

    for循环 格式: for(表达式1;循环条件;表达式2) { 循环体; } 解释:先执行表达式1,再判断循环条件是否为真,如果为真则执行循环体,执行完成后再执行表达式2 再次判断循环条件,由此一直反 ...

  6. linux 命令之netstat

    转自:http://www.maomao365.com/?p=699 linux 命令之netstat 在linux中netstat命令的作用是查看TCP/IP网络当前所开放端口,所对应的本地和外地端 ...

  7. c/c++ 智能指针 shared_ptr 和 new结合使用

    智能指针 shared_ptr 和 new结合使用 用make_shared函数初始化shared_ptr是最推荐的,但有的时候还是需要用new关键字来初始化shared_ptr. 一,先来个表格,唠 ...

  8. DELL 服务器报CPU 1 has an internal error (IERR)

    重启服务器,然后按F2 进入到bios中,选择system bios settings这个选项,然后选择system profiles 进入,在这个里面麻烦将 C1E 和Cstate这两个选项改为di ...

  9. ASP.NET -- WebForm -- HttpRequest类的方法和属性

    ASP.NET -- WebForm --  HttpRequest类的方法和属性 1. HttpRequest类的方法(1) BinaryRead: 执行对当前输入流进行指定字节数的二进制读取. ( ...

  10. 制作CSS绚烂效果的三种属性

    animation(动画).transition(过渡).transform(变形) https://www.cnblogs.com/shenfangfang/p/5713564.html