Thread中常用API

1、sleep方法

  线程的 sleep 方法会使线程休眠指定的时间长度。休眠的意思是,当前逻辑执行到此不再继续执行,而是等待指定的时间。但在这段时间内,该线程持有的锁并不会释放。这样设计很好理解,因为线程在 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 方法。而传入的纳秒会被四舍五入。

2、yield方法

  yield方法意为让路,线程调用yield方法,则表明该线程愿意放弃占用的cpu资源,当然这只是给cpu提醒一下,如果cpu资源并不紧张,则会忽略该提醒,若cpu没有忽略这个提醒,该线程状态就由RUNNING 变为 RUNNABLE 状态,其他状态为RUNNABLE的线程可以抢夺cpu资源,该线程也可以加入抢夺cpu资源。开发中此方法并不常用。

3、currentThread 方法

  这是一个静态方法,用于获取当前线程的实例。用法很简单,如下:

Thread.currentThread();

  拿到线程的实例后,我们还可以获取 Thread 的 名称:

Thread.currentThread().getName();

  此外我们还可以获取线程 ID :

Thread.currentThread().getId();

4、setPriority 方法

  用于设置线程的优先级,优先级高的线程,更容易抢夺到资源,如果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 进行设置。

5、interrupt方法

  interrupt方法意为中断,注意中断的并不是线程的逻辑,而是线程的阻塞。它并不会让线程中断,中断的是可中断方法,如sleep方法。

public class InterruptClient {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for(int i=0; i<100 ;i++){
                System.out.println("I'm doing my work");
                System.out.println("I'm interrupted?"+Thread.currentThread().isInterrupted());
            }
        });
        thread.start();
        Thread.sleep(1);
        thread.interrupt();
    }

  线程 run 方法中没有调用可中断方法,只是输出 I’m doing my work,另外还会输出自己的中断状态。而主线程会 sleep 一毫秒,留时间给 thread 线程启动,然后调用 thread 线程的 interrupt 方法。截取其中关键一段输出如下:

I'm doing my work
I'm interrupted?false
I'm doing my work
I'm interrupted?true
I'm doing my work
I'm interrupted?true

  这段后面的输出一直到结束,都在重复 “I’m doing my work I’m interrupted?true“,这说明两个问题:

  1. 调用 interrupt 方法,并不会影响可中断方法之外的逻辑。线程不会中断,会继续执行。这里的中断概念并不是指中断线程;
  2. 一旦调用了 interrupt 方法,那么线程的 interrupted 状态会一直为 ture(没有通过调用可中断方法或者其他方式主动清除标识的情况下);

  通过上面实现了解了 interrupt 方法中断的不是线程。它中断的其实是可中断方法,如 sleep 。可中断方法被中断后,会把 interrupted 状态归位,改回 false 。

  看代码:

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(1000);
                } 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 。

  最后我们再看一下静态方法 interrupted 。这个方法其实和成员方法 isInterrupted 方法类似,都是返回了 interrupted 状态。不同就是 interrupted 方法返回状态后,如果为 true 则会清除掉状态。而 isInterrupted 则不会。上面第一段测试代码已经验证了这一点,被打断后,调用 isInterrupted 一直返回 true。

  下面验证下 interrupted 是否会清除标识位。把第一段代码稍微改一下:

public class InterruptedClient {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for(int i=0; i<100 ;i++){
                System.out.println("I'm doing my work");
//原代码  System.out.println("I'm interrupted?"+Thread.currentThread().isInterrupted());
                System.out.println("I'm interrupted?"+Thread.interrupted());
            }
        });

        thread.start();
        Thread.sleep(1);
        thread.interrupt();
    }
}

  输出结果如下:

I'm doing my work
I'm interrupted?false
I'm doing my work
I'm interrupted?false
I'm doing my work
I'm interrupted?true
I'm doing my work
I'm interrupted?false
I'm doing my work
I'm interrupted?false

  可以看到在输出 “I’m interrupted?true” 后,中断状态又变回了 false。

  通过以上,可以看出 interrupt 方法只是设置了中断标识位,这个标识位只对可中断方法会产生作用。不过我们还可以利用它做更多的事情,比如说如果线程的 run 方法中这么写:

while(!isInterrupted()){
//do somenting
}

  这样主线程中可以通过调用此线程的 interrupt 方法,让其退出运行。此时 interrupted 的含义就真的是线程退出了。不过假如你的 while 循环中调用了可中断方法,那么就会有干扰。

6、join方法

  最后我们再说一个重要的方法 join。这个方法功能强大,也很实用。我们用它能够实现并行化处理。比如主线程需要做两件没有相互依赖的事情,那么可以起 A、B 两个线程分别去做。通过调用 A、B 的 join 方法,让主线程 block 住,直到 A、B 线程的工作全部完成,才继续走下去。我们来看下面这段代码:

public class JoinClient {
    public static void main(String[] args) throws InterruptedException {
        Thread backendDev = createWorker("backed dev", "backend coding");
        Thread frontendDev = createWorker("frontend dev", "frontend coding");
        Thread tester = createWorker("tester", "testing");

        backendDev.start();
        frontendDev.start();
      
//        backendDev.join();
//        frontendDev.join();
      
        tester.start();
    }

    public static Thread createWorker(String role, String work) {
        return new Thread(() -> {
            System.out.println("I finished " + work + " as a " + role);
        });
    }
}

  这段代码中,我们把 join 方法去掉。执行结果如下:

I finished backend coding as a backed dev
I finished testing as a tester
I finished backend coding as a frontend dev

  我们期望的是前端和后端开发完成工作后,测试才开始测试。但从输出结果看并非如此。要想实现这个需求,我们只需把注释打开,让 backendDev 和 frontendDev 先做 join 操作,此时主线程会被 block 住。直到 backendDev 和 frontendDev 线程都执行结束,才会继续往下执行。输出如下:

I finished backend coding as a backed dev
I finished frontend coding as a frontend dev
I finished testing as a tester

  可以看到现在的输出完全符合我们的期望。可见调用 join 方法后 block 的并不是被调用的 backendDev 或 frontendDev 线程,而是调用方线程,这个需要牢记。

总结:

  Thread还有其他的API,比如废弃的stop,关于其他的API可以阅读Thread源码,注释写的很详细。

  

posted @ 2021-07-20 15:41  卯仙  阅读(159)  评论(0编辑  收藏  举报