控制线程

摘要

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. leetcode-38.报数

    leetcode-38.报数 题意 报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数.其前五项如下: 1. 1 2. 11 3. 21 4. 1211 5. 111221 1 被读作 ...

  2. 关于谷歌JSV8与微软JSRT的性能比较

    首先,我并没有得到实际的比较结果,望有兴趣的朋友可以帮助完成这个比较. benchmarksgame,提供了各种语言的性能比较,但都为linux下的测试,很难比较谷歌与微软的东西. 众所周知,JSV8 ...

  3. Spark数据倾斜及解决方案

    一.场景 1.绝大多数task执行得都非常快,但个别task执行极慢.比如,总共有100个task,97个task都在1s之内执行完了,但是剩余的task却要一两分钟.这种情况很常见. 2.原本能够正 ...

  4. Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库

    1.第一步,在本地数据库中建一个与服务器同名的数据库 2.第二步,右键源数据库,任务>导出数据,弹出导入导出提示框,点下一步继续 3.远程数据库操作,确认服务器名称(服务器地址).身份验证(输入 ...

  5. 以太坊之——golang以太坊接口调用

    Go语言具有简单易学.功能强大,可跨平台编译等众多优势,所以这里选择以Go语言切入以太坊. 开始之前需要以下环境: Ubuntu(这里以ubuntu16.04为例) geth Ubuntu16.04安 ...

  6. Mycat实现Mysql主从读写分离

    一.概述 关于Mycat的原理网上有很多,这里不再详述,对于我来说Mycat的功能主要有如下几种: 1.Mysql主从的读写分离 2.Mysql大表分片 3.其他数据库例如Oracle,MSSQL,D ...

  7. c/c++ 标准库 map set 大锅炖

    标准库 map set 大锅炖 一,关联容器有哪些 按关键字有序保存元素 map 保存key和value set 只保存key mulutimap key可以重复出现 multiset key可以重复 ...

  8. 磁盘测试工具FIO工具安装和使用方法

    一.FIO工具安装: 1.查看fio是否安装 [root@localhost /]#rpm –qa|grep fio 2.源码安装(推荐) 官网地址:http://freecode.com/proje ...

  9. IPerf——网络测试工具介绍与源码解析(5)

    本篇随笔讲述一下TCP协议下,双向测试模式和交易测试模式下客户端和服务端执行的情况: 双向测试模式: 官方文档的解释 Run Iperf in dual testing mode. This will ...

  10. "敏捷革命"读书笔记

    最近看可一本书 书名叫<敏捷革命>外国著作中文翻译 本来想自己总结读后感但是本书后面都有本章的总结,所以下面都已摘抄为主,以备之后快速浏览 第一章 世界的运作方式已经打破 规划是有用的,而 ...