Java多线程学习(四)

时间:2022-03-04 08:05:27
控制线程

摘要

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.          // i等于20时,使用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】。

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