前言

如果让一个线程进入休眠?
我们第一个想到的就是Thread.sleep(long millis)方法吧!
如果你对synchronized足够了解的话,那么你肯定还能想到Object.wait()方法。
再如果你对java的Lock锁足够了解的话,那么你还能想到LockSupport类的park()方法。
那么我们接下来就来对比分析一下这三种方式的使用场景和背后的区别,方便我们以后使用的时候,选择一个最合适的方式来处理我们的代码。


Thread.sleep()

1.看看JDK中此方法上面的注释

Thread.sleep()方法必须有一个参数,此参数表示休眠的时长。
此方法会使得当前线程进入TIMED_WAITING状态,但是不会释放当前线程所持有的锁。
另外,此方法还会抛出一个InterruptedException的异常,这是受检查异常,调用者必须处理。而且当exception被抛出来的时候,这个“interrupted status”也会被清除。这句话怎么理解呢?等会我们举个例子看一下。
下面我看一下此方法上面的注释都说了什么内容:
在这里插入图片描述

2.案例展示(线程状态:TIMED_WAITING)

代码如下(展示状态为:TIMED_WAITING):

package com.kinyang;

/**
 * @author KinYang.Lau
 * @date 2021/6/10 6:37 下午
 */
public class ThreadSleepTest {

    public static void main(String[] args) throws InterruptedException {
         创建一个 t1 线程
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("t1 线程执行 sleep()方法... ");
                    /// 线程 进入休眠 5s
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1-thread");
         启动t1线程
        t1.start();
        /// 这里让主线程休眠1s后,再查看 t1 线程的状态,确保t1线程已经执行
        Thread.sleep(1000L);
        System.out.println("t1 线程当前状态:"+t1.getState());

    }

}

输出结果我们看到,执行sleep之后,线程是TIMED_WAITING状态。
在这里插入图片描述

3.案例展示(InterruptedException异常会清除interrupted status)

代码如下:

package com.kinyang;

/**
 * @author KinYang.Lau
 * @date 2021/6/10 6:37 下午
 */
public class ThreadSleepTest {

    public static void main(String[] args) throws InterruptedException {
         创建一个 t1 线程
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("t1 线程执行 sleep()方法,休眠5s ");
                    /// 线程 进入休眠 5s
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    System.out.println("捕获到 InterruptedException 异常!");
                    System.out.println("查看 t1.线程的 interrupted status :"+Thread.interrupted());
                }
            }
        },"t1-thread");
         启动t1线程
        t1.start();
        /// 这里让主线程休眠1s后,再查看 t1 线程的状态,确保t1线程已经执行
        Thread.sleep(100L);
        System.out.println("t1 线程当前状态:"+t1.getState());
        System.out.println("此时执行:interrupt()方法 强制打断线程...");
        t1.interrupt();
    }
}

运行结果:
在这里插入图片描述
输出结果我们看到,线程被打断后,捕获异常,然后打印状态居然是 false。
从表面上看,我们打断了线程,线程的interrupt 状态应该是 打断状态呀,也就是应该是 true!但是这里居然是false。为什么?
原因就是被重置了!!就是上看到的jdk里面sleep方法上面的注解一样,当捕sleep获异常的时候,会把状态清除掉。sleep方法上的注解原话是:

	 * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     * 翻译:如果有任何线程打断当前线程的话,当 异常(exception)被抛出的时候,
     * 线程的interrupted status 值会被清除掉,也就是会变为true

也就是说,sleep方法抛出的异常,会把线程的打断状态清除掉。
那么我们是怎么知道线程有这个状态呢?如果不调用sleep方法,我们打断线程,看看这个interrupt status会不是true? 我们再写个例子

public class ThreadSleepTest2 {
    public static void main(String[] args) throws InterruptedException {
         创建一个 t1 线程
        Thread t1 = new Thread(() -> {
            while (true){
                if (Thread.interrupted()){
                    System.out.println("t1 线程被打断了!我们马上查看 Thread的 interrupted status :"+Thread.interrupted());
                }else {
                    System.out.println("t1 线程正常执行一项耗时操作,看电影....");
                    watchMV();
                }
            }
        },"t1-thread");
         启动t1线程
        t1.start();
        /// 这里让主线程休眠1s后,再查看 t1 线程的状态,确保t1线程已经执行
        Thread.sleep(1000L);
        System.out.println("t1 线程当前状态:"+t1.getState());
        System.out.println("此时执行:interrupt()方法 强制打断线程...");
        t1.interrupt();
    }

    /**
     * 此方法,模拟一个耗时的操作
     */
    public static void watchMV(){
        try {
            FileInputStream inputStream = new FileInputStream("/Users/xxxx.mp4/");
            ///  这里将byte数组设置小一些,好让此方法执行慢点,可以看出效果
            byte[] bytes = new byte[10];
            // 读入多个字节到字节数组中,read为一次读入的字节数
            while (inputStream.read(bytes) != -1) {
            }
            System.out.println("看完了!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果
在这里插入图片描述
从结果中我们可以看到,当执行了t1.interrupt();方法后,t1线程的看电影方法依然在执行,并未受到影响,但是当方法执行完成后,再次while循环的时候,执行Thread.interrupted()方法进行判断的时候,得到的结果是true,所以程序输出了t1 线程被打断了!我们马上查看 Thread的 interrupted status 。但是在这句输出的最后我们再调用Thread.interrupted()得到的结果却是 false
这说明了什么???
说明了Thread.interrupted()方法执行了以后,会改变Thread的 interrupted status 的值。
我们看一下这个方法的注释。

/**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     翻译:测试当前线程是否已经被打断了。 
     此方法调用后,会清除掉打断状态(interrupted status)
	换句话说,如果此方法连续调用两次,第二次将会返回false(除非在调用第二次方法之前,线程又被打断)。
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

从这个案例我们可以看到,线程如果被interrupted的话,是会有状态变化的,证明了线程存在这个状态。
现在我们回过头来sleep方法注释上面写的下面这段话就能理解所谓的interrupted status是什么意思了。

if any thread has interrupted the current thread. 
The <i>interrupted status</i> of the current thread is cleared 
when this exception is thrown.`

try catch sleep的方法后,执行Thread.interrupted()得到的结果永远都是false,因为catch后会清除了打断状态。

4.案例展示(sleep不会释放monitors锁)

package com.kinyang.thread;

import java.util.Scanner;

/**
 * @author KinYang.Lau
 * @date 2021/6/10 6:37 下午
 */
public class ThreadSleepTest3 {

    private static Object monitor = new Object();
    public static void main(String[] args) throws InterruptedException {
         创建一个 t1 线程
        Thread t1 = new Thread(() -> {
            System.out.println("t1 尝试获取锁");
            synchronized (monitor){
                System.out.println("t1 获取到锁");
                System.out.println("t1 执行 Thread.sleep() 方法,休眠该线程!");
                /// 我们执行 sleep 操作,让线程进入休眠,看看其他线程是否能抢到 monitor 锁,
                ///  如果其他线程可以获取到 monitor 锁后,那么说明sleep会释放monitor锁
                ///  否则,说明sleep不会释放monitor锁
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 线程执行完成");
            }
        },"t1-thread");
         启动t1线程
        t1.start();
        /// 这里让主线程休眠1s后,确保t1线程已经执行,并获取到锁
        Thread.sleep(1000L);
        Thread t2 = new Thread(() -> {
            System.out.println("t2 尝试获取锁");
            synchronized (monitor){
                System.out.println("t2 获取到锁");
                System.out.println("执行 t2 方法");
            }
        },"t2-thread");
        t2.start();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            if (scanner.nextLine().equalsIgnoreCase("bye")){
                System.out.println("再见");
                break;
            }else {
                System.out.println("指令错误");
            }

        }
    }

}

执行结果
在这里插入图片描述
我们可以看出 t1 线程sleep后,t2 尝试去获取锁,但是没有获取到,一直到t1线程执行完成后,t2才获取到锁。
这也说明了,t1线程的sleep方法,并不会释放掉 monitor锁。

这一点以后我们会和Object.wait()方法做对比,做完对比后你就知道什么是释放锁,什么是不释放锁了。


总结

简单总结一下,Thread的sleep方法的特性是:
1、必须指定休眠时长,休眠后线程转为TIMED_WAITING状态
2、sleep方法会抛出一个受检查异常InterruptedException,我们必须try catch处理。
3、sleep抛出异常后,会清除掉“线程打断状态”。所以在catch的代码块里,执行Thread.interrupted()得到的结果永远都是false
4、sleep不会丢掉monitors,也就是不会释放锁。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐