线程:Thread类源码解析(下)

Thread类源码解析

  • 4.Thread API
    • sleep方法
    • yield方法
    • setPriority方法
    • interrupt方法*
      • 1)中断非阻塞线程
      • 2)中断阻塞线程
    • Join方法

4.Thread API

sleep方法

sleep 方法会使线程休眠指定的时间长度。

休眠的意思是,当前逻辑执行到此不再继续执行,而是等待指定的时间。但在这段时间内,该线程持有的 monitor 锁(monitor锁见synchronized笔记)并不会被放弃。可以认为线程只是工作到一半休息了一会,但它所占有的资源并不会交还。

这样设计是因为线程在 sleep 的时候可能是处于同步代码块的中间位置,如果此时把锁放弃,就违背了同步的语义。所以 sleep 时并不会放弃锁,等过了 sleep 时长后,可以确保后面的逻辑还在同步执行。

sleep方法有两个重载,

public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException

两者的区别只是一个支持休眠时间到毫秒级,另外一个到纳秒级。但其实第二个并不能真的精确到纳秒级别。第二个重载方法代码,

public static void sleep(long millis, int nanos)
throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}

可以清楚的看到,最终调用的还是第一个毫秒级别的 sleep 方法。而传入的纳秒会被四舍五入。如果大于 50 万(0.5毫秒),毫秒++,否则纳秒被省略。

yield方法

public static native void yield();

该方法是一个native方法,意思是当前线程做出让步,放弃当前 cpu使用权,让 cpu 重新选择线程,避免线程过度使用 cpu。

在写 while 死循环的时候,预计短时间内 while 死循环可以结束的话,可以在循环里面使用 yield 方法,防止 cpu 一直被 while 死循环霸占。

有点需要说明的是,让步不是绝不执行,重新竞争时,cpu 也有可能重新选中自己。

setPriority方法

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

Thread 有的最小和最大优先级数值,范围在 1-10。如果不在此范围内,则会报错。

如果设置的 priority 超过了线程所在组的 priority ,那么只能被设置为组的最高 priority 。最后通过调用 native 方法 setPriority0 进行设置。

interrupt方法*

一个线程在未正常结束之前, 被强制终止是很危险的事情,可能带来完全预料不到的严重后果。比如持有锁的线程被强制终止后未进行释放使得其他线程始终无法访问资源等。 所以在Thread类的源代码中Thread.suspend, Thread.stopThread.resume等方法都被标注为Deprecated。

在多线程开发中会遇到如下需求,

  1. 有必要让一个线程死掉
  2. 结束线程的某种等待的状态

直接终止线程是不可行的,安全且间接实现上面两个需求的方法有两种,

  • 使用等待/通知机制,见synchronized笔记
  • 给线程一个中断信号, 让它自己决定该怎么办

被调用interrupt方法的线程,其中断状态会被修改。执行过程取决于线程在被调用interrupt方法时的状态,如,

  1. 对于一个非阻塞状态的线程而言,interrupt方法只改变中断状态,线程被调用该方法后返回 true
  2. 若调用interrupt方法之前,线程调用了Object#wait()Thread#join()Thread#sleep(long) 这些方法处于 WAITING 或 TIMED_WAITING状态。此时对线程调用interrupt方法,就会抛出 InterruptedException 异常,抛出异常的原因是没有占用CPU(waitjoin方法)或占用CPU缺不运行(sleep方法)的线程是不可能给自己的中断状态置位的
  3. 不是所有的阻塞方法收到中断后都可以取消阻塞状态,如果 I/O 操作被阻塞了,主动打断当前线程,会抛出 ClosedByInterruptException 异常,且在被中断的情况下也不会退出阻塞状态

故interrupt 方法的作用是让可中断的方法中断(如sleep方法)。中断的并不是线程的逻辑,中断的是线程的阻塞。这一点要彻底搞清。

1)中断非阻塞线程

public class InterruptClient {
    public void run(){  
        while(true){  
            if(Thread.currentThread().isInterrupted()){  
                System.out.println("Someone interrupted me.");  
            }  
            else{  
                System.out.println("Thread is Going...");  
            }
        }  
    }  

    public static void main(String[] args) throws InterruptedException {  
        InterruptClient t = new InterruptClient ();  
        t.start();  
        Thread.sleep(3000);  
        t.interrupt();  
    }  
}

上面的代码执行后,

  • 在主线程sleep的过程中由于子线程线程t中isInterrupted()返回结果为false,所以不断的输出 “Thread is going”
  • 当子线程的interrupt()方法被调用后,t线程中isInterrupted()返回结果变为为true。此时会输出 “Someone interrupted me.” 。但是线程并不会因为中断信号而停止运行。因为它只是被修改一个中断信号而已

明确两个概念,

  1. 调用 interrupt 方法,并不会影响可非阻塞状态的线程。非阻塞状态的线程不会停止,会继续执行。interrupt中断的是线程的某一部分业务逻辑,前提是线程需要检查自己的中断状态
  2. 一旦调用了 interrupt 方法,那么线程的 interrupted 状态会一直为 ture(没有通过调用可中断方法或者其他方式主动清除标识的情况下)

2)中断阻塞线程

public class InterruptSleepClient {
    public static void main(String[] args) throws InterruptedException {
        Thread xiaopang = new Thread(()->{
            for(int i=0; i<100 ;i++){
                System.out.println("I'm doing my work");
                try {
                    System.out.println("I will sleep");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    System.out.println("My sleeping was interrupted");
                }
                System.out.println("I'm interrupted?"+Thread.currentThread().isInterrupted());
            }
        });
        xiaopang.start();
        Thread.sleep(1);
        xiaopang.interrupt();
    }
}

执行结果,

I’m doing my work
I will sleep
My sleeping was interrupted
I’m interrupted? false
I’m doing my work
I will sleep
I’m interrupted? false
I’m doing my work
I will sleep
I’m interrupted? false

可以看到当 xiaopang.interrupt () 执行后,睡眠中的 xiaopang 被唤醒了。

这里额外需要注意的是,此时 xiaopang 线程的 interrupted 状态还是 false 。因为可中断线程会捕获中断的信号,并且会清除掉 interrupted 标识。因此输出的 “I’m interrupted ?” 全部是 false 。

Join方法

join 的意思就是当前线程等待另一个线程执行完成之后,才能继续操作。

public void join() throws Exception {
  Thread main = Thread.currentThread();
  log.info("{} is run。",main.getName());
  
  Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
      log.info("{} begin run",Thread.currentThread().getName());
      try {
        Thread.sleep(30000L);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      log.info("{} end run",Thread.currentThread().getName());
    }
  });
  // 开一个子线程去执行
  thread.start();
  // 当前主线程等待子线程执行完成之后再执行
  thread.join();
  log.info("{} is end", Thread.currentThread());
}

执行的结果,就是主线程在执行 thread.join (); 代码后会停住,子线程沉睡 30 秒后执行完毕,此后再执行主线程。这里的 join 的作用就是让主线程等待子线程执行完成。整个过程示意图如下,
线程:Thread类源码解析(下)_第1张图片
从图中可以看出,主线程一直等待子线程执行完毕后才执行。在等待期间,主线程的状态也是 TIMED_WAITING

你可能感兴趣的:(Java源码)