当前位置:   article > 正文

【面试精讲】Java线程6种状态和工作原理详解,Java创建线程的4种方式_java 线程池里的线程状态

java 线程池里的线程状态

Java线程6种状态和工作原理详解,Java创建线程的4种方式

目录

一、Java线程的六种状态

二、Java线程是如何工作的?

三、BLOCKED 和 WAITING 的区别

四、start() 和 run() 源码分析

五、Java创建线程的所有方式和代码详解

1. 继承Thread类

2. 实现Runnable接口

3. 实现Callable接口与FutureTask

4. 使用线程池

总结

 博主v:XiaoMing_Java


在并发编程领域,Java线程是实现多任务处理的基石。了解其状态及工作原理对于开发高效、稳定的Java应用至关重要。本文将深入探讨Java线程的各种状态以及它们的工作机制。

一、Java线程的六种状态

Java线程在其生命周期内可以处于以下几种状态:

  1. 新建(New): 线程被创建后,但在调用start()方法之前的状态。
  2. 可运行(Runnable): 线程已经启动,并且执行start()方法后的状态。在此状态,线程可能正在运行也可能正在等待CPU分配时间片,具体取决于操作系统的线程调度器。
  3. 阻塞(Blocked): 当一个线程试图获取一个对象的锁(不是通过synchronized块或方法),而该锁被其他线程持有,则该线程会进入阻塞状态。
  4. 等待(Waiting): 当一个线程等待另一个线程执行特定动作时(例如,通过Object.wait()Thread.join()LockSupport.park()),该线程会进入等待状态。
  5. 超时等待(Timed Waiting): 类似于等待状态,但有最大等待时间限制。当线程调用带有指定等待时间的sleep()wait()join(), 或LockSupport.parkNanos()/parkUntil()时,会处于该状态。
  6. 终止(Terminated): 线程的运行结束,无论是正常完成还是中途退出。
  1. public enum State {
  2.     /**
  3.      * 新建状态,线程被创建出来,但尚未启动时的线程状态
  4.      */
  5.     NEW,
  6.     /**
  7.      * 就绪状态,表示可以运行的线程状态,但它在排队等待来自操作系统的 CPU 资源
  8.      */
  9.     RUNNABLE,
  10.     /**
  11.      * 阻塞等待锁的线程状态,表示正在处于阻塞状态的线程,正在等待监视器锁,比如等待执行 synchronized 代码块或者
  12.      * 使用 synchronized 标记的方法
  13.      */
  14.     BLOCKED,
  15.     /**
  16.      * 等待状态,一个处于等待状态的线程正在等待另一个线程执行某个特定的动作。
  17.      * 例如,一个线程调用了 Object.wait() 它在等待另一个线程调用
  18.      * Object.notify() 或 Object.notifyAll()
  19.      */
  20.     WAITING,
  21.     /**
  22.      * 计时等待状态,和等待状态 (WAITING) 类似,只是多了超时时间,比如
  23.      * 调用了有超时时间设置的方法 Object.wait(long timeout) 和 
  24.      * Thread.join(long timeout) 就会进入此状态
  25.      */
  26.     TIMED_WAITING,
  27.     /**
  28.      * 终止状态,表示线程已经执行完成
  29.      */
  30. }

二、Java线程是如何工作的?

Java线程的工作机制与操作系统的线程管理紧密相关。Java虚拟机(JVM)通过映射到底层操作系统的原生线程实现来管理Java线程。这个过程主要包括线程的创建、调度、上下文切换、以及终止等方面。

  • 创建: 当在Java程序中创建线程对象并调用其start()方法时,JVM会向操作系统请求创建一个新的线程。操作系统为此分配资源并开始执行线程的run()方法。
  • 调度与执行: 一旦线程处于可运行状态,它就成为了操作系统调度器的候选对象。基于操作系统的策略和当前情况(如优先级、CPU亲和性等),调度器选择线程并分配CPU时间片进行执行。在任意给定时刻,单核CPU只能执行一个线程,而多核CPU可以并行执行多个线程。
  • 上下文切换: 当当前执行的线程由于时间片耗尽、等待IO操作、试图获取锁等原因需要被暂停时,操作系统保存当前线程的状态(称为“上下文”),并恢复另一个线程的上下文以继续执行。这个保存和恢复的过程称为上下文切换。
  • 等待与通知: 当线程进入等待状态时,它会释放所有持有的锁,直到其他线程通过特定的通知机制(如notify()notifyAll()方法)将其唤醒。此时,线程再次竞争获取必要的锁以继续执行。
  • 终止: 线程执行完毕后,或者因为未捕获的异常而终止,JVM会清理线程所占用的所有资源,并将线程标记为终止状态。

线程的工作模式是,首先先要创建线程并指定线程需要执行的业务方法,然后再调用线程的 start() 方法,此时线程就从 NEW(新建)状态变成了 RUNNABLE(就绪)状态,此时线程会判断要执行的方法中有没有 synchronized 同步代码块,如果有并且其他线程也在使用此锁,那么线程就会变为 BLOCKED(阻塞等待)状态,当其他线程使用完此锁之后,线程会继续执行剩余的方法。

当遇到 Object.wait() 或 Thread.join() 方法时,线程会变为 WAITING(等待状态)状态,如果是带了超时时间的等待方法,那么线程会进入 TIMED_WAITING(计时等待)状态,当有其他线程执行了 notify() 或 notifyAll() 方法之后,线程被唤醒继续执行剩余的业务方法,直到方法执行完成为止,此时整个线程的流程就执行完了,执行流程如下图所示:

三、BLOCKED 和 WAITING 的区别

虽然 BLOCKED 和 WAITING 都有等待的含义,但二者有着本质的区别,首先它们状态形成的调用方法不同,其次 BLOCKED 可以理解为当前线程还处于活跃状态,只是在阻塞等待其他线程使用完某个锁资源;而 WAITING 则是因为自身调用了 Object.wait() 或着是 Thread.join() 又或者是 LockSupport.park() 而进入等待状态,只能等待其他线程执行某个特定的动作才能被继续唤醒,比如当线程因为调用了 Object.wait() 而进入 WAITING 状态之后,则需要等待另一个线程执行 Object.notify() 或 Object.notifyAll() 才能被唤醒。

四、start() 和 run() 源码分析

首先从 Thread 源码来看,start() 方法属于 Thread 自身的方法,并且使用了 synchronized 来保证线程安全

  1. public synchronized void start() {
  2.     // 状态验证,不等于 NEW 的状态会抛出异常
  3.     if (threadStatus != 0)
  4.         throw new IllegalThreadStateException();
  5.     // 通知线程组,此线程即将启动
  6.     group.add(this);
  7.     boolean started = false;
  8.     try {
  9.         start0();
  10.         started = true;
  11.     } finally {
  12.         try {
  13.             if (!started) {
  14.                 group.threadStartFailed(this);
  15.             }
  16.         } catch (Throwable ignore) {
  17.             // 不处理任何异常,如果 start0 抛出异常,则它将被传递到调用堆栈上
  18.         }
  19.     }
  20. }

run() 方法为 Runnable 的抽象方法,必须由调用类重写此方法,重写的 run() 方法其实就是此线程要执行的业务方法

  1. public class Thread implements Runnable {
  2.  // 忽略其他方法......
  3.   private Runnable target;
  4.   @Override
  5.   public void run() {
  6.       if (target != null) {
  7.           target.run();
  8.       }
  9.   }
  10. }
  11. @FunctionalInterface
  12. public interface Runnable {
  13.     public abstract void run();
  14. }

五、Java创建线程的所有方式和代码详解

Java提供了多种创建和管理线程的方式,从简单的Thread类继承到实现RunnableCallable接口,再到使用强大的线程池管理。

选择合适的方式取决于具体的应用场景和需求。对于简单的任务,直接使用Thread类或Runnable接口可能就足够了。而对于需要任务执行结果的情况,Callable接口将是更好的选择。

在处理大量并发任务时,利用线程池可以显著提高性能和资源利用率。了解和掌握这些方法,对于编写高效、稳定的Java多线程程序至关重要。

1. 继承Thread

Java允许通过继承Thread类的方式创建线程。这种方式简单直观,适用于简单的线程任务。

  1. class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. // 这里填写线程任务代码
  5. System.out.println("线程运行中...");
  6. }
  7. }
  8. public class ThreadExample {
  9. public static void main(String[] args) {
  10. MyThread t = new MyThread();
  11. t.start(); // 启动新线程
  12. }
  13. }

2. 实现Runnable接口

实现Runnable接口是创建线程最常见的方式之一,它提供了更大的灵活性,允许线程类继承其他类。

  1. class MyRunnable implements Runnable {
  2. @Override
  3. public void run() {
  4. // 线程任务
  5. System.out.println("通过Runnable接口运行线程...");
  6. }
  7. }
  8. public class RunnableExample {
  9. public static void main(String[] args) {
  10. Thread t = new Thread(new MyRunnable());
  11. t.start(); // 启动线程
  12. }
  13. }

3. 实现Callable接口与FutureTask

Callable接口类似于Runnable,但它可以返回执行结果,并且能抛出异常。

  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.ExecutionException;
  3. import java.util.concurrent.FutureTask;
  4. class MyCallable implements Callable<Integer> {
  5. @Override
  6. public Integer call() throws Exception {
  7. // 返回执行结果
  8. return 123;
  9. }
  10. }
  11. public class CallableExample {
  12. public static void main(String[] args) throws InterruptedException, ExecutionException {
  13. FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
  14. Thread t = new Thread(futureTask);
  15. t.start(); // 启动线程
  16. // 获取执行结果
  17. Integer result = futureTask.get();
  18. System.out.println("Callable返回的结果: " + result);
  19. }
  20. }

4. 使用线程池

Java的Executor框架提供了一个强大的线程池管理机制,能更高效地管理线程生命周期。

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. public class ThreadPoolExample {
  4. public static void main(String[] args) {
  5. ExecutorService executor = Executors.newFixedThreadPool(5); // 创建固定大小的线程池
  6. for (int i = 0; i < 10; i++) {
  7. Runnable worker = new MyRunnable();
  8. executor.execute(worker); // 提交任务给线程池执行
  9. }
  10. executor.shutdown(); // 关闭线程池
  11. while (!executor.isTerminated()) {
  12. // 等待所有任务完成
  13. }
  14. System.out.println("所有线程已完成任务");
  15. }
  16. }

总结

理解Java线程的状态及其转换是掌握Java并发编程的关键。正确地管理线程状态,合理利用同步机制,可以有效提高Java应用的性能和响应速度。探讨Java线程的六种状态、Java线程是如何工作的,Java创建线程【继承Thread类、实现Runnable接口、实现Callable接口与FutureTask、使用线程池】的所有方式

如果本文对你有帮助 欢迎 关注 、点赞 、收藏 、评论, 博主才有动力持续记录遇到的问题!!!

 博主v:XiaoMing_Java

  

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/357785
推荐阅读
相关标签