线程睡眠sleep方法

线程睡眠sleep方法

  • [sleep 方法](#sleep 方法)
  • [sleep 与锁的关系代码示例](#sleep 与锁的关系代码示例)
  • [sleep 期间被中断抛出异常](#sleep 期间被中断抛出异常)
  • [sleep 方法的应用场景](#sleep 方法的应用场景)

sleep 方法

当当前执行线程调用sleep方法时,该线程会主动放弃 CPU 的执行权并进入TIMED_WAITING(计时等待)状态(属于阻塞状态的子集),且在指定的时间范围内不再参与 CPU 的抢占式调度;在此期间,该线程不会释放已持有的任何对象监视器锁或其他同步资源;当指定的等待时间耗尽,或线程被其他线程通过 interrupt() 方法中断时,该方法会终止等待:

  • 若时间耗尽:线程从 TIMED_WAITING 状态切换至 RUNNABLE(就绪)状态,等待 CPU 调度器分配执行时间片后恢复执行;
  • 若被中断:方法会立即抛出InterruptedException异常,线程退出 TIMED_WAITING 状态,需由开发者处理该异常以决定后续执行逻辑。

方法签名:

方法归属与调用语义:

  • public static void sleep(long millis) 与 public static void sleep(long millis,int nanos) 均被 static 关键词修饰了;
  • sleep() 是 java.lang.Thread 类的静态方法,其调用行为严格绑定到当前执行该方法的线程(即调用上下文所在线程),而非任意线程对象:即使通过线程实例(如 t1.sleep(1000))调用,效果仍为当前执行该行代码的线程进入睡眠状态,而非目标线程实例 t1。

执行时序与线程状态转换:

  • 调用该方法后,当前线程立即放弃 CPU 执行权,从 RUNNABLE(就绪 / 运行)状态切换至 TIMED_WAITING(计时等待)状态,在此期间不参与 CPU 的抢占式调度;
  • 当指定时间阈值耗尽后,线程从 TIMED_WAITING 状态恢复为 RUNNABLE 状态,但需等待 CPU 调度器分配时间片后才能真正恢复执行,因此实际暂停时长可能略长于入参指定值,具体取决于 CPU 负载与调度策略,sleep() 的入参定义了线程暂停执行的最小时间阈值,而非精确执行时长。

同步锁持有特性:

若线程在持有同步锁的状态下调用 sleep(),该线程在睡眠期间不会释放已持有的任何同步锁资源:

  • 对于 synchronized 隐式锁:线程持续持有对象监视器锁,其他线程无法竞争获取该锁,直至当前线程退出同步代码块 / 方法;
  • 对于 ReentrantLock 等显式锁:线程持续持有锁,其他线程调用 lock() 会阻塞,直至当前线程显式调用 unlock() 释放锁。

中断响应与异常处理:

sleep() 是可中断方法,其行为遵循 Java 线程中断机制:

  • 若当前线程处于 sleep() 引发的 TIMED_WAITING 状态时,其他线程调用该线程的 interrupt() 方法触发中断,当前线程会立即终止睡眠,在 sleep() 调用处抛出 java.lang.InterruptedException;
  • 抛出该异常时,JVM 会自动清除当前线程的中断状态,可以通过 Thread.currentThread().isInterrupted() 验证;
  • 因 InterruptedException 为检查型异常,调用 sleep() 时必须显式处理:要么通过 try-catch 块捕获并处理异常,要么在当前方法签名中通过 throws 关键字声明抛出该异常;
  • 该异常是线程间协作的合法信号,用于实现线程间的中断协作,而非错误场景。

wait () 与 sleep (long timeout) 的核心区别

特性 wait(long timeout) sleep(long timeout)
锁行为 调用时立即释放所持有的监视器锁 全程不释放任何锁(包括监视器锁、显式锁)
唤醒机制 支持超时唤醒、notify/notifyAll 主动唤醒、中断唤醒 仅支持超时唤醒、中断唤醒(无主动唤醒机制)
恢复执行前提 需重新竞争并获取监视器锁后才能继续执行 无需竞争锁,超时 / 中断后直接继续执行
调用上下文 必须在 synchronized 同步环境中调用 可在任意上下文调用
所属类 Object 类(所有对象均可调用) Thread 类(仅线程对象 / 静态调用)

sleep 与锁的关系代码示例

Thread.sleep() 的核心行为是放弃 CPU 执行权(线程进入 TIMED_WAITING 状态,暂时不参与 CPU 调度),这个行为与是否在 synchronized 代码块中无关。

情景一:非 synchronized 代码块中的 sleep

java 复制代码
public static void main(String[] args) {
    while(true){
        System.out.println(new Date());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行逻辑:主线程执行 System.out.println() 打印当前时间;调用 sleep(1000):主线程立即让出 CPU 执行权,进入计时等待状态,CPU 可以去调度其他线程;1 秒后,主线程从 TIMED_WAITING 切换为 RUNNABLE(就绪)状态,等待 CPU 重新分配时间片;拿到 CPU 时间片后,主线程继续执行循环,再次打印时间。

如果没有 sleep(),这个死循环会瞬间占满一个 CPU 核心,而加了 sleep() 后,CPU 利用率会极低,这也是服务器端用 sleep() 的核心目的:避免空循环耗尽 CPU 资源。

情景二:synchronized 代码块中的 sleep

java 复制代码
synchronized (ojb){
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

执行逻辑:线程先竞争并获取 obj 的监视器锁,进入同步块;调用 sleep(1000),线程依然会让出 CPU 执行权,但始终持有 obj 的锁;这 1 秒内,CPU 可以调度其他非同步代码的线程运行,但任何需要获取 obj 锁的线程都会被阻塞(因为当前线程没释放锁);1 秒后,线程就绪并重新获取 CPU 时间片,执行完同步块后释放 obj 锁。

我们看到的其他线程拿不到锁无法执行同步代码的效果,这是锁未释放导致的,而非 CPU 没被让出,此时 CPU 完全可以去执行其他不需要 obj 锁的线程。

在独占锁(ReentrantLock)场景下 sleep 不会释放锁:

java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SleepTest {

    // 创建一个独占锁
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        //创建线程A
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                //获取独占锁
                lock.lock();
                try {
                    System.out.println("child threadA is in sleep");
                    Thread.sleep(1000);
                    System.out.println("child threadA is in awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    lock.unlock();
                }
            }
        });

        //创建线程B
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                //获取独占锁
                lock.lock();
                try {
                    System.out.println("child threadB is in sleep");
                    Thread.sleep(1000);
                    System.out.println("child threadB is in awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    lock.unlock();
                }
            }

        });
        //启动线程
        threadA.start();
        threadB.start();
    }
}

如上代码首先创建了一个独占锁,然后创建了两个线程,每个线程在内部先获取锁,然后睡眠,睡眠结束后会释放锁。首先,无论执行多少次上面的代码都是线程A先输出或者线程B先输出,不会出现线程A和线程B交叉输出的情况。从执行结果来看,线程A先获取了锁,那么线程A会先输出一行,然后调用sleep 方法让自己睡眠1s,在线程A睡眠的这1s内那个独占锁lock还是线程A自己持有,线程B会一直阻塞直到线程A醒来后执行unlock释放锁。

ReentrantLock 是独占锁(排他锁),同一时刻只能有一个线程成功调用 lock() 获取锁,其他线程调用 lock() 时会进入阻塞状态,直到锁被释放。sleep 对显式锁(Lock)和隐式锁(synchronized)的表现相同,都不释放。

sleep 期间被中断抛出异常

java 复制代码
public class SleepTest1 {

    public static void main(String[] args) throws InterruptedException {
        //创建线程A
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("child threadA is in sleep");
                    Thread.sleep(5000);
                    System.out.println("child threadA is in awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动线程
        threadA.start();
        Thread.sleep(2000);
        threadA.interrupt();
    }
}

线程 A 启动后,先打印 child threadA is in sleep,然后进入 sleep 状态,主线程等待 2 秒后,调用 threadA.interrupt() 中断线程 A,线程 A 的 sleep(5000) 被立即终止,抛出 InterruptedException,进入 catch 块打印异常堆栈,线程 A 不会执行 System.out.println("child threadA is in awaked"),因为 sleep 被中断后直接跳转到异常处理逻辑。

sleep 方法的应用场景

场景 1:控制线程执行节奏

通过 sleep () 让线程按预设频率执行任务,既满足业务对执行间隔的要求又避免线程高频抢占 CPU 资源,是 sleep () 最基础的应用场景。

典型场景:接口限流(控制线程每秒发起的外部接口请求数量)、定时轮询、模拟用户操作延迟。

注意事项:间隔时长需平衡业务时效性与资源消耗,过短仍会占用较多 CPU,过长则降低任务响应效率。

场景 2:优化空循环

当线程需循环等待某个条件满足(如等待文件生成、等待信号量触发、等待异步结果返回)时,空循环会导致 CPU 核心利用率接近 100%,sleep () 可通过阻塞线程大幅降低 CPU 消耗。

典型应用:循环等待文件生成、等待异步任务完成;

注意事项:避免在 synchronized/Lock 同步块内执行 "循环 + sleep",因 sleep () 不释放锁,会导致其他竞争锁的线程长时间阻塞。

场景 3:简单的线程时序协调

在低复杂度多线程场景中,通过 sleep () 实现线程间基础的时序控制,让一个线程临时等待另一个线程完成关键操作。

典型场景:主线程等待子线程完成初始化、让线程按指定顺序执行(临时方案)。

注意事项:

仅适用于临时、低精度的时序控制,复杂同步场景需优先使用 Object.wait ()、CountDownLatch、CyclicBarrier 等同步工具;

避免设置固定长时长 sleep,若目标线程提前完成,当前线程仍会等待 sleep 结束,造成资源浪费。

场景 4:调试 / 测试场景

在开发、测试阶段,通过 sleep () 模拟业务执行的耗时场景,复现并发环境下的线程安全、超时、资源竞争等问题。

典型应用:模拟接口调用延迟、复现并发问题;

注意事项:仅用于开发 / 测试阶段,生产环境严禁通过 sleep () 模拟业务耗时(会阻塞线程,降低服务并发能力)。

场景 5:后台守护线程

后台守护线程(如日志收集、系统心跳检测、临时文件清理)需持续运行但低频执行,sleep () 是降低其资源开销的核心工具。

典型应用:心跳检测线程、日志收集线程;

注意事项:守护线程需处理 InterruptedException,确保进程退出时线程能优雅终止。

相关推荐
gjxDaniel4 小时前
Bash编程语言入门与常见问题
开发语言·bash
汤姆yu4 小时前
基于springboot的植物花卉销售管理系统
java·spring boot·后端
zhooyu4 小时前
OpenGL 与 C++:深入理解与实现 Transform 组件
开发语言·c++
不想写bug呀4 小时前
RabbitMQ相关问题(1)
java·rabbitmq
海南java第二人4 小时前
Spring Boot Starters深度解析:简化依赖管理的核心利器
java·spring boot·后端
captain3764 小时前
Java-链表
java·开发语言·链表
tqs_123454 小时前
跳出多层循环的方式
java·开发语言
froginwe114 小时前
媒体查询:现代网页设计的核心工具
开发语言
东方轧线4 小时前
突破锁竞争的性能枷锁:深度剖析 C++ 内存模型与无锁编程在超大规模并行 AI 系统中的极致应用实践
java·c++·人工智能