写于:2019-11-09

Thread 有很多 API ,下面介绍几种常用的 API,更多信息参考Java Doc

一、setDaemon:设置守护线程

作用:将线程设置为守护线程

01.png
1、该方法将线程设置为守护线程。

2、该方法的调用必须要在线程 start 之前。

小贴士 当 JVM 中没有非守护线程时,JVM 进程会退出。

1、什么是守护线程

一个简单的代码

  1. public class SimpleThread {
  2. public static void main(String[] args) {
  3. // JVM 中只要存在一个非守护线程,JVM 就不会退出
  4. // === main 线程开始
  5. // 创建一个线程
  6. Thread thread = new Thread(() -> {
  7. while (true) {
  8. sleep(1_000);
  9. }
  10. });
  11. ////////////////////////////////////////
  12. thread.setDaemon(true); // 设置为守护线程
  13. ////////////////////////////////////////
  14. thread.start();
  15. // === main 线程结束
  16. }
  17. public static void sleep(long mill){
  18. try {
  19. Thread.sleep(mill);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }

上述代码中存在两个线程,一个 main 线程,一个 thread 线程。

a、thread 默认为非守护线程,即使 main 线程结束了,JVM 也不会退出

b、调用 setDaemon 方法,将 thread 设置为守护线程,此时 main 线程为 thread 父线程,也是主线程,thread 会随着 main 线程生命周期的结束,而结束。此时由于没有非守护线程存在,JVM 退出。

2、守护线程的作用

如果一个 JVM 没有一个非守护线程,此时 JVM 会退出,也就是说守护线程具备自动结束生命周期的特征。

守护线程适合用于执行一些后台任务,当关闭某些线程,或者退出 JVM 时,这些守护线程会跟着退出。

二、sleep:线程进入休眠

作用:让线程进入休眠

02.png
上述的两种方法都是让线程进入休眠,只是精度不同。

小贴士 当线程进入休眠时,此时并不会放弃 monitor 锁的所有权。

三、yield:线程发出释放CPU执行权信号

作用:让调用的线程放弃 CPU 执行权。使得线程从 RUNNING 状态进入 RUNNABLE 状态。

小贴士 在 CPU 资源不紧张时,调用 yield 方法,CPU 可能不予以理会。

四、setPriority:设置优先级

作用:设置线程优先级。让线程更有机会获取到CPU执行权,但并一定会,该操作仅仅是对 CPU 发出了一个提示。

1、线程优先级概念

进程有进程的优先级,线程也有线程的优先级,理论上优先级比较高的线程更有机会获取到CPU的执行权,但是设置线程优先级,仅仅是给 CPU 发送了一个提示操作,具体如下:

  • 对于 root 用户,该方法会提示 操作系统你想要设置的优先级级别。
  • 如果 CPU 比较忙,设置优先级可能会获取到更多的 CPU 时间片,但是闲暇时优先级的高低几乎不起作用。

2、通过源码进一步了解优先级设置相关信息

代码如下

  1. public class Thread implements Runnable {
  2. /** The minimum priority that a thread can have. **/
  3. public final static int MIN_PRIORITY = 1;
  4. /** The maximum priority that a thread can have. **/
  5. public final static int MAX_PRIORITY = 10;
  6. public final void setPriority(int newPriority) {
  7. ThreadGroup g;
  8. checkAccess();
  9. if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
  10. throw new IllegalArgumentException();
  11. }
  12. if((g = getThreadGroup()) != null) {
  13. if (newPriority > g.getMaxPriority()) {
  14. newPriority = g.getMaxPriority();
  15. }
  16. setPriority0(priority = newPriority);
  17. }
  18. }
  19. }

通过代码得出结论如下:

  • a、线程的优先级值取值为 [1,10]
  • b、设置的优先级不能大于线程组的最大优先级。如: ThreadGroup maxPriority=7,那么线程设定的 priority 只能到 7

五、getId:获取线程ID

作用:获取线程的唯一ID,该 ID 在整个 JVM 进程中是唯一的。

通过源码看看线程ID生成规则

  1. public class Thread implements Runnable {
  2. /* For generating thread ID */
  3. private static long threadSeqNumber;
  4. private void init(ThreadGroup g, Runnable target, String name,
  5. long stackSize, AccessControlContext acc,
  6. boolean inheritThreadLocals) {
  7. ......
  8. // 设置线程 ID
  9. tid = nextThreadID();
  10. }
  11. private static synchronized long nextThreadID() {
  12. return ++threadSeqNumber;
  13. }
  14. }

从代码能够知道,线程 ID 的生成规则:从0开始,以此递增。

六、currentThread:获取当前线程

作用:返回当前执行线程的引用

  1. public class Thread implements Runnable {
  2. public static native Thread currentThread();
  3. }

七、interrupt:中断线程

作用:打断当前线程的阻塞状态。

如下方法,会使得线程阻塞,通过 interrupt 能够中断这种阻塞

  • Object 的 wait 和 wait(long) 方法
  • Thread 的 sleep(long) 和 sleep(long,int) 方法
  • Thread 的 join(long) 和 join(long,int) 方法
  • InterruptibleChaanel 的 io 操作
  • Selector 的 wakeup 方法
  • 其他方法

一旦线程在阻塞的状态下被打断,都会抛出一个 InterruptedException 的异常。

小贴士 1、线程被打断,并不意味着,线程的生命周期结束了。 2、对一个生命周期结束的线程, interrupt 方法并不起作用。

阻塞的线程被中断后,中断标志会被复位

在 Thread 内维护一个中断标识,在线程调用中断时,该标志为被设定为 true,一个简单的案例如下:
03.jpg

上述案例中的被中断的线程并没有被阻塞,所以被中断之后,标志位总为 true。

小贴士 如果调用的是 interrupted 标志位会被复位,参考:《isinterrupted-对比-interrupted》

案例:在上述代码的线程中加入阻塞,然后对阻塞进行中断
04.jpg
通过代码和控制台打印能够知道,阻塞方法进行中断捕获处理后,中断标志位会被复位为 false。

八、join

作用:以 main 线程起一个 A 线程为例。调用 A.join ,此时 main 线程会被阻塞直到 A 线程的生命周期结束。或者等待时间结束。

05.jpg
简单案例

  1. public class SimpleThread {
  2. public static void main(String[] args) throws InterruptedException {
  3. // 创建线程
  4. List<Thread> threads = IntStream.rangeClosed(1, 2).mapToObj(name -> {
  5. return new Thread(() -> {
  6. for (int i = 0; i < 5; i++) {
  7. System.out.println(Thread.currentThread().getName() + "#" + i);
  8. try {
  9. TimeUnit.MILLISECONDS.sleep(2);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }, name + "");
  15. }).collect(Collectors.toList());
  16. threads.forEach(Thread::start); // 启动线程
  17. for (Thread thread : threads) { // join
  18. thread.join();
  19. }
  20. for(int i = 0;i < 5;i++){
  21. System.out.println(Thread.currentThread().getName() + "#" +i);
  22. }
  23. }
  24. }

控制台打印

  1. 2#0
  2. 1#0
  3. 2#1
  4. 1#1
  5. 1#2
  6. 2#2
  7. 1#3
  8. 2#3
  9. 1#4
  10. 2#4
  11. main#0
  12. main#1
  13. main#2
  14. main#3
  15. main#4

通过案例能够验证,main 下的线程 1、2 调用 join 之后,main 会等到他们结束生命周期才会接着执行 main 线程的内容

join(long) 在等待一定时长之后,便不再阻塞,