您的位置:首页 > 汽车 > 时评 > 深入理解Java中的线程状态转换及关键同步方法

深入理解Java中的线程状态转换及关键同步方法

2024/9/19 2:30:23 来源:https://blog.csdn.net/qq_42798343/article/details/142286621  浏览:    关键词:深入理解Java中的线程状态转换及关键同步方法

深入理解Java中的线程状态转换及关键同步方法

在现代软件开发中,多线程编程是实现高效、响应式应用程序的关键技术之一。理解线程的生命周期及其状态转换,对于编写健壮、性能优越的并发程序至关重要。本文将深入探讨Java中线程的各种状态转换,并详细解释joinsleepwaitpark等关键方法的作用与使用场景。

目录

  1. Java线程概述
  2. 线程状态详解
    • NEW(新建状态)
    • RUNNABLE(可运行状态)
    • BLOCKED(阻塞状态)
    • WAITING(等待状态)
    • TIMED_WAITING(计时等待状态)
    • TERMINATED(终止状态)
  3. 线程状态转换图
  4. 关键同步方法解析
    • join() 方法
    • sleep() 方法
    • wait() 方法
    • park() 方法
    • interrupt 方法
  5. 示例代码
  6. 最佳实践与注意事项
  7. 总结

Java线程概述

在Java中,线程是执行程序中最小的执行单元。Java通过java.lang.Thread类和java.lang.Runnable接口提供了对线程的支持。线程允许程序并行执行多个任务,从而提升应用程序的性能和响应性。

线程的生命周期由多个状态组成,理解这些状态及其转换对于掌握多线程编程至关重要。


线程状态详解

Java为线程定义了六种状态,通过java.lang.Thread.State枚举类来表示:

  1. NEW(新建状态)
  2. RUNNABLE(可运行状态)
  3. BLOCKED(阻塞状态)
  4. WAITING(等待状态)
  5. TIMED_WAITING(计时等待状态)
  6. TERMINATED(终止状态)

NEW(新建状态)

  • 描述:线程对象已被创建,但尚未启动。
  • 触发条件:通过new Thread()创建一个线程对象后,线程处于NEW状态。
Thread thread = new Thread(() -> {// 线程任务
});
System.out.println(thread.getState()); // 输出: NEW

RUNNABLE(可运行状态)

  • 描述:线程已经启动,并在Java虚拟机中准备运行,或正在运行。
  • 触发条件:调用Thread.start()方法后,线程进入RUNNABLE状态。
  • 注意RUNNABLE状态包括正在运行(实际获得CPU时间片)和等待调度的情况。
thread.start();
System.out.println(thread.getState()); // 输出: RUNNABLE

BLOCKED(阻塞状态)

  • 描述:线程试图获取一个对象的监视器锁(锁竞争),无法继续执行。
  • 触发条件:线程进入RUNNABLE状态后,试图获取一个被其他线程锁定的对象锁。
synchronized(lock) {// 线程A持有锁
}Thread threadB = new Thread(() -> {synchronized(lock) {// 线程B试图获取锁,若锁被线程A持有,则进入BLOCKED状态}
});
threadB.start();

WAITING(等待状态)

  • 描述:线程无限期地等待另一个线程来执行特定操作。
  • 触发条件
    • 调用Object.wait()方法,没有指定超时时间。
    • 调用Thread.join()方法,没有指定超时时间。
    • 调用LockSupport.park()方法。
synchronized(lock) {lock.wait(); // 线程进入WAITING状态
}

TIMED_WAITING(计时等待状态)

  • 描述:线程在等待另一个线程的特定操作,且等待时间有限。
  • 触发条件
    • 调用Object.wait(long timeout)方法,指定超时时间。
    • 调用Thread.sleep(long millis)方法。
    • 调用Thread.join(long millis)方法,指定超时时间。
    • 调用LockSupport.parkNanos(long nanos)LockSupport.parkUntil(long deadline)方法。
Thread.sleep(1000); // 线程进入TIMED_WAITING状态

TERMINATED(终止状态)

  • 描述:线程已经完成执行或因异常而终止。
  • 触发条件:线程的run()方法执行完毕或因未捕获的异常导致线程终止。
thread.join();
System.out.println(thread.getState()); // 输出: TERMINATED

线程状态转换图

在这里插入图片描述


关键同步方法解析

理解线程状态转换离不开对关键同步方法的掌握。下面详细解释joinsleepwaitpark等方法的作用及其对线程状态的影响。

join() 方法

详细解析

join()方法用于让一个线程等待另一个线程完成执行。它有三个重载版本:

  1. join():无限期等待,直到目标线程终止。
  2. join(long millis):等待指定的毫秒数,或者直到目标线程终止。
  3. join(long millis, int nanos):等待指定的毫秒数和纳秒数,或者直到目标线程终止。
示例
public class JoinExample {public static void main(String[] args) {Thread thread = new Thread(() -> {try {Thread.sleep(2000); // 模拟任务System.out.println("子线程执行完毕");} catch (InterruptedException e) {e.printStackTrace();}});thread.start();try {thread.join(); // 等待子线程完成System.out.println("主线程继续执行");} catch (InterruptedException e) {e.printStackTrace();}}
}

输出

子线程执行完毕
主线程继续执行
关键点
  • join()确保了线程的顺序执行。
  • 可以通过join(long millis)控制等待时间,防止线程永久阻塞。

sleep() 方法

详细解析

sleep()方法使当前线程暂停执行指定的时间。它有两个重载版本:

  1. sleep(long millis):暂停指定的毫秒数。
  2. sleep(long millis, int nanos):暂停指定的毫秒数和纳秒数。
示例
public class SleepExample {public static void main(String[] args) {System.out.println("主线程开始睡眠");try {Thread.sleep(1000); // 睡眠1秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程醒来");}
}

输出

主线程开始睡眠
主线程醒来
关键点
  • sleep()不会释放锁,即使在synchronized块中调用。
  • sleep()是静态方法,作用于当前线程。

wait() 方法

详细解析

wait()方法使当前线程等待,直到被其他线程调用notify()notifyAll()方法。它有三个重载版本:

  1. wait():无限期等待。
  2. wait(long timeout):等待指定的毫秒数。
  3. wait(long timeout, int nanos):等待指定的毫秒数和纳秒数。
示例
public class WaitNotifyExample {private static final Object lock = new Object();public static void main(String[] args) {Thread consumer = new Thread(() -> {synchronized (lock) {try {System.out.println("消费者等待数据...");lock.wait(); // 进入WAITING状态System.out.println("消费者收到通知,继续执行");} catch (InterruptedException e) {e.printStackTrace();}}});Thread producer = new Thread(() -> {synchronized (lock) {try {Thread.sleep(2000); // 模拟数据生成} catch (InterruptedException e) {e.printStackTrace();}System.out.println("生产者生成数据,通知消费者");lock.notify(); // 唤醒消费者线程}});consumer.start();producer.start();}
}

输出

消费者等待数据...
生产者生成数据,通知消费者
消费者收到通知,继续执行
关键点
  • wait()必须在synchronized块或方法内调用,确保线程持有对象锁。
  • 调用wait()会释放对象锁,允许其他线程进入synchronized块。
  • notify()notifyAll()唤醒后,线程会重新竞争锁,获取锁后继续执行。

park() 方法

详细解析

park()方法是LockSupport类提供的高级线程阻塞机制,允许线程被阻塞,直到被其他线程调用unpark()或被中断。它有两种主要形式:

  1. park():无限期等待。
  2. parkNanos(long nanos)parkUntil(long deadline):有时间限制的等待。
示例
import java.util.concurrent.locks.LockSupport;public class ParkUnparkExample {public static void main(String[] args) throws InterruptedException {Thread parker = new Thread(() -> {System.out.println("Parker 线程被阻塞");LockSupport.park(); // 进入 WAITING 状态System.out.println("Parker 线程被唤醒");});parker.start();Thread.sleep(1000); // 主线程等待1秒System.out.println("主线程调用 unpark() 唤醒 Parker 线程");LockSupport.unpark(parker);}
}

输出

Parker 线程被阻塞
主线程调用 unpark() 唤醒 Parker 线程
Parker 线程被唤醒
关键点
  • park()unpark()是配对使用的,unpark()可以在park()之前调用,导致park()不阻塞。
  • 不需要在synchronized块内调用,可以灵活地用于复杂的并发控制。
  • park()不会抛出异常,必须通过其他机制(如中断状态)来控制线程。

interrupt 方法

详细解析

在Java中,interrupt机制用于中断线程的阻塞状态。通过调用线程的interrupt()方法,可以向线程发送一个中断信号,通知线程它应该停止当前的阻塞操作并尽快退出。

interrupt 的作用
  • 中断阻塞状态的线程:当线程处于WAITINGTIMED_WAITINGBLOCKED状态时,调用interrupt()会使线程抛出InterruptedException,从而打断阻塞状态。
  • 设置中断状态:如果线程不处于阻塞状态,调用interrupt()会设置线程的中断状态标志,但不会立即中断线程的执行。
isInterrupted()interrupted() 方法的区别

Java中提供了两个方法来检查线程的中断状态:isInterrupted()interrupted()。它们的区别如下:

  1. isInterrupted() 方法

    • 用途:检查线程是否被中断。
    • 行为:不会清除中断状态。
    • 使用场景:适用于需要多次检查中断状态的情况。
    • 示例
    Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {// 执行任务}System.out.println("线程被中断,退出循环");
    });thread.start();
    thread.interrupt();
    
  2. interrupted() 方法

    • 用途:检查当前线程是否被中断。
    • 行为:会清除当前线程的中断状态。
    • 使用场景:适用于只需要一次性检查中断状态的情况。
    • 示例
    Thread thread = new Thread(() -> {while (!Thread.interrupted()) {// 执行任务}System.out.println("线程被中断,退出循环");
    });thread.start();
    thread.interrupt();
    
示例
public class InterruptExample {public static void main(String[] args) {Thread thread = new Thread(() -> {try {System.out.println("线程开始睡眠");Thread.sleep(5000); // 进入 TIMED_WAITING 状态} catch (InterruptedException e) {System.out.println("线程被中断,捕获 InterruptedException");}// 检查中断状态if (Thread.currentThread().isInterrupted()) {System.out.println("线程中断状态为 true");} else {System.out.println("线程中断状态为 false");}// 使用 interrupted() 方法boolean interrupted = Thread.interrupted();System.out.println("调用 interrupted() 后,中断状态为: " + interrupted);});thread.start();try {Thread.sleep(1000); // 主线程等待1秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程调用 interrupt() 中断子线程");thread.interrupt();}
}

输出

线程开始睡眠
主线程调用 interrupt() 中断子线程
线程被中断,捕获 InterruptedException
线程中断状态为 false
调用 interrupted() 后,中断状态为: false

解释

  1. 子线程开始执行并进入睡眠状态,进入TIMED_WAITING
  2. 主线程等待1秒后调用interrupt()中断子线程。
  3. 子线程在睡眠期间被中断,抛出InterruptedException并捕获。
  4. 捕获异常后,子线程检查中断状态,发现已经被清除(isInterrupted()返回false)。
  5. 调用Thread.interrupted()再次检查中断状态,返回false,并清除中断状态标志。
关键点
  • isInterrupted()
    • 检查线程的中断状态,不会清除状态。
    • 适用于需要多次检查的场景。
  • interrupted()
    • 检查当前线程的中断状态,并清除状态。
    • 适用于一次性检查的场景。

方法对比总结

方法所属类用途阻塞状态释放锁唤醒方式其他特点
join()java.lang.Thread等待另一个线程完成执行WAITING / TIMED_WAITING目标线程终止或超时用于线程顺序控制,主线程等待子线程
sleep()java.lang.Thread暂停当前线程执行指定时间TIMED_WAITING睡眠时间到达或被中断不依赖于任何锁,常用于定时任务
wait()java.lang.Object等待其他线程的通知WAITING / TIMED_WAITINGnotify()notifyAll()必须在synchronized块内调用,释放锁
park()java.util.concurrent.locks.LockSupport阻塞当前线程,直到被unpark唤醒WAITING / TIMED_WAITINGunpark() 或被中断更灵活,非synchronized,可用于高级并发工具
interrupt()java.lang.Thread中断线程的阻塞状态或设置中断标志触发阻塞线程抛出InterruptedExceptioninterrupt()调用者可以用于打断阻塞状态,设置中断标志

详细对比

  1. 阻塞条件与唤醒方式

    • join():依赖于另一个线程的终止状态或超时自动唤醒。
    • sleep():基于时间自动唤醒,不依赖于任何外部事件。
    • wait():依赖于其他线程的notify()notifyAll()调用。
    • park():依赖于其他线程的unpark()调用或线程被中断。
    • interrupt():通过调用interrupt()方法可以中断正在阻塞的线程,如wait()sleep()park()等。
  2. 锁的释放

    • wait():在调用时会释放对象锁,允许其他线程进入synchronized块。
    • join()sleep()park()interrupt():不释放任何锁。
  3. 调用环境

    • wait():必须在synchronized块或方法内调用。
    • join()sleep()park()interrupt():不需要在synchronized块内调用(除了wait()必须在同步环境中)。
  4. 灵活性与适用场景

    • join():适用于线程间顺序执行的场景。
    • sleep():适用于需要暂停线程执行的场景,如定时任务或延时操作。
    • wait():适用于线程间需要等待特定条件的场景,如生产者-消费者模型。
    • park():适用于需要更灵活控制线程阻塞与唤醒的高级并发场景,通常在构建并发框架时使用。
    • interrupt():适用于需要中断阻塞线程或设置中断标志的场景,用于控制线程的执行流程。

示例代码

以下示例代码展示了joinsleepwaitparkinterrupt方法的区别与使用。

public class SyncMethodsComparison {private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {// 示例1:join()Thread thread1 = new Thread(() -> {try {Thread.sleep(1000);System.out.println("Thread1 完成执行");} catch (InterruptedException e) {e.printStackTrace();}});thread1.start();thread1.join(); // 主线程等待 thread1 完成System.out.println("主线程在 join() 后继续执行");// 示例2:sleep()Thread thread2 = new Thread(() -> {try {System.out.println("Thread2 开始睡眠");Thread.sleep(2000);System.out.println("Thread2 睡眠结束");} catch (InterruptedException e) {e.printStackTrace();}});thread2.start();thread2.join(); // 等待 thread2 完成System.out.println("主线程在 sleep() 后继续执行");// 示例3:wait() 和 notify()Thread consumer = new Thread(() -> {synchronized (lock) {try {System.out.println("消费者等待数据...");lock.wait(); // 进入 WAITING 状态System.out.println("消费者收到通知,继续执行");} catch (InterruptedException e) {System.out.println("消费者被中断");}}});Thread producer = new Thread(() -> {try {Thread.sleep(1000); // 模拟数据生成} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock) {System.out.println("生产者生成数据,通知消费者");lock.notify(); // 唤醒消费者线程}});consumer.start();producer.start();consumer.join();producer.join();System.out.println("主线程在 wait()/notify() 后继续执行");// 示例4:park() 和 unpark()Thread parker = new Thread(() -> {System.out.println("Parker 线程被阻塞");LockSupport.park(); // 进入 WAITING 状态System.out.println("Parker 线程被唤醒");});parker.start();Thread.sleep(1000); // 主线程等待1秒System.out.println("主线程调用 unpark() 唤醒 Parker 线程");LockSupport.unpark(parker);parker.join();System.out.println("主线程在 park()/unpark() 后继续执行");// 示例5:interrupt() 方法Thread interrupter = new Thread(() -> {try {System.out.println("Interrupter 线程开始睡眠");Thread.sleep(5000); // 进入 TIMED_WAITING 状态} catch (InterruptedException e) {System.out.println("Interrupter 线程被中断,捕获 InterruptedException");}// 检查中断状态if (Thread.currentThread().isInterrupted()) {System.out.println("Interrupter 线程中断状态为 true");} else {System.out.println("Interrupter 线程中断状态为 false");}// 使用 interrupted() 方法boolean interrupted = Thread.interrupted();System.out.println("调用 interrupted() 后,中断状态为: " + interrupted);});interrupter.start();Thread.sleep(1000); // 主线程等待1秒System.out.println("主线程调用 interrupt() 中断 interrupter 线程");interrupter.interrupt();interrupter.join();System.out.println("主线程在 interrupt() 后继续执行");}
}

输出

Thread1 完成执行
主线程在 join() 后继续执行
Thread2 开始睡眠
Thread2 睡眠结束
主线程在 sleep() 后继续执行
消费者等待数据...
生产者生成数据,通知消费者
消费者收到通知,继续执行
主线程在 wait()/notify() 后继续执行
Parker 线程被阻塞
主线程调用 unpark() 唤醒 Parker 线程
Parker 线程被唤醒
主线程在 park()/unpark() 后继续执行
Interrupter 线程开始睡眠
主线程调用 interrupt() 中断 interrupter 线程
Interrupter 线程被中断,捕获 InterruptedException
Interrupter 线程中断状态为 false
调用 interrupted() 后,中断状态为: false
主线程在 interrupt() 后继续执行

解释

  1. join() 示例

    • 主线程启动thread1并调用join(),主线程等待thread1完成后继续执行。
  2. sleep() 示例

    • 主线程启动thread2thread2进入睡眠2秒,醒来后完成执行。
    • 主线程通过join()等待thread2完成,然后继续执行。
  3. wait()notify() 示例

    • consumer线程进入synchronized块并调用wait(),进入WAITING状态。
    • producer线程等待1秒后进入synchronized块,调用notify()唤醒consumer线程。
    • consumer线程被唤醒后继续执行。
  4. park()unpark() 示例

    • parker线程调用LockSupport.park()进入WAITING状态。
    • 主线程等待1秒后调用LockSupport.unpark(parker)唤醒parker线程。
    • parker线程被唤醒后继续执行。
  5. interrupt() 方法示例

    • interrupter线程开始睡眠5秒,进入TIMED_WAITING状态。
    • 主线程等待1秒后调用interrupter.interrupt()中断interrupter线程。
    • interrupter线程在睡眠期间被中断,抛出InterruptedException并捕获。
    • 捕获异常后,interrupter线程检查中断状态,发现已被清除(isInterrupted()返回false)。
    • 调用Thread.interrupted()再次检查中断状态,返回false,并清除中断状态标志。

最佳实践与注意事项

  1. 避免死锁

    • 确保多个线程获取锁的顺序一致。
    • 尽量减少锁的持有时间。
    • 使用超时机制(如tryLock)防止长时间等待锁。
  2. 合理使用synchronized

    • 只在必要的代码块使用synchronized,避免过度同步导致性能下降。
    • 优先使用更高级的并发工具,如ReentrantLock,以获得更灵活的锁控制。
  3. 理解wait()notify()的配合使用

    • wait()notify()必须在synchronized块内调用,确保线程持有对象锁。
    • 使用while循环检查条件,以防止虚假唤醒。
    synchronized (lock) {while (!condition) {lock.wait();}// 继续执行
    }
    
  4. 合理使用park()unpark()

    • LockSupport提供了更灵活的线程阻塞机制,但需要谨慎使用,避免线程永远被阻塞。
  5. 使用join()确保线程执行顺序

    • 在需要等待某个线程完成后再继续执行的场景下,使用join()方法确保执行顺序。
  6. 避免使用过期的并发工具

    • 尽量使用Java提供的高级并发工具(如CountDownLatchSemaphoreCyclicBarrier等)代替低级别的synchronizedwait/notify机制,以提高代码的可读性和可维护性。
    CountDownLatch latch = new CountDownLatch(1);Thread worker = new Thread(() -> {// 处理任务latch.countDown(); // 任务完成,减少计数
    });worker.start();// 等待任务完成
    latch.await();
    
  7. 处理中断

    • 在可能阻塞的方法(如wait()sleep()park())中,正确处理中断信号,确保线程能够及时响应中断。
    • 避免忽略InterruptedException,应根据业务需求决定如何处理中断。
    try {Thread.sleep(1000);
    } catch (InterruptedException e) {// 处理中断,如清理资源、退出线程等Thread.currentThread().interrupt(); // 重新设置中断状态
    }
    

总结

理解Java中线程的状态及其转换机制,是掌握多线程编程的基础。通过熟悉NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED等线程状态,以及joinsleepwaitparkinterrupt等关键方法的作用与使用场景,开发者可以更高效地设计和实现并发程序。

正确理解和使用这些同步方法,能够帮助开发者更好地控制线程行为,避免常见的并发问题,如死锁、竞态条件等。同时,结合Java提供的高级并发工具,可以编写出更高效、可维护的多线程程序。

希望本文能够帮助你深入理解Java中的线程状态转换及关键同步方法,并在实际开发中灵活应用。如有任何问题或需要进一步探讨,欢迎在评论区留言交流!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com