线程的几种状态转换

本文详细解析了线程从创建到死亡的整个生命周期,包括线程的五种基本状态:新建、就绪、运行、阻塞和死亡。阐述了线程在不同状态之间的转换过程,以及如何通过线程的内置方法如wait()、notify()、sleep()和join()来控制线程的行为。

线程在一定条件下,状态会发生变化。线程一共有以下几种状态:

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,

(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程变化的状态转换图如下:

注:拿到对象的锁标记,即为获得了对该对象(临界区)的使用权限。即该线程获得了运行所需的资源,进入“就绪状态”,只需获得CPU,就可以运行。因为当调用wait()后,线程会释放掉它所占有的“锁标志”,所以线程只有在此获取资源才能进入就绪状态。
下面小小的作下解释: 
1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样,  当我们new了这个对象后,线程就进入了初始状态; 
2、当该对象调用了start()方法,就进入就绪状态; 
3、进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态; 
4、进入运行状态后情况就比较复杂了 
    4.1、run()方法或main()方法结束后,线程就进入终止状态; 
    4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,进程让出CPU,然后就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源即调用sleep ()函数后,线程不会释放它的“锁标志”。)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配CPU时间片。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
    4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间片从而需要转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
   4.4、当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入就绪状态,等待OS分配CPU时间片;

4.5. suspend() 和 resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。 
   4.6、wait()和 notify() 方法:当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。 

wait() 使得线程进入阻塞状态,它有两种形式:

一种允许指定以毫秒为单位的一段时间作为参数;另一种没有参数。前者当对应的 notify()被调用或者超出指定时间时线程重新进入可执行状态即就绪状态,后者则必须对应的 notify()被调用。当调用wait()后,线程会释放掉它所占有的“锁标志”从而使线程所在对象中的其它synchronized数据可被别的线程使用。waite()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronizedblock中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

 

注意区别:初看起来wait() 和 notify() 方法与suspend()和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的suspend()及其它所有方法在线程阻塞时都不会释放占用的锁(如果占用了的话),而wait() 和 notify() 这一对方法则相反。

上述的核心区别导致了一系列的细节上的区别

首先,前面叙述的所有方法都隶属于 Thread类,但是wait() 和 notify() 方法这一对却直接隶属于 Object 类也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

其次,前面叙述的所有方法都可在任何位置调用,但是wait() 和 notify() 方法这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常。

wait() 和 notify()方法的上述特性决定了它们经常和synchronized方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block和wake up 原语(这一对方法均声明为 synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。

关于 wait() 和 notify() 方法最后再说明两点:

第一:调用notify() 方法导致解除阻塞的线程是从因调用该对象的 wait()方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

第二:除了notify(),还有一个方法 notifyAll()也可起到类似作用,唯一的区别在于,调用 notifyAll()方法将把因调用该对象的 wait()方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend()方法和不指定超时期限的wait()方法的调用都可能产生死锁。遗憾的是,Java并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。

<think>好的,我需要向用户介绍线程几种状态。首先,我得回忆一下线程的基本状态有哪些。记得Java中线程状态有新建、可运行、阻塞、等待、超时等待和终止这六个状态,但可能不同的编程语言或操作系统会有差异,用户可能想知道通用的状态还是特定语言的。用户的问题没有特别指明,所以我应该以通用的角度来解释,同时也可以提一下常见的不同实现。 接下来,需要确保结构清晰,分步骤说明每个状态。先从新建状态开始,当线程被创建但还未启动的时候。然后是就绪或者可运行状态,等待CPU分配时间片。之后是运行状态,正在执行。不过有时候操作系统会把就绪和运行合并为可运行状态,比如在Java中。接着是阻塞状态,比如等待I/O操作或者获取锁。然后是等待状态线程主动等待某个条件,可能需要其他线程唤醒。超时等待是带有时间限制的等待。最后是终止状态线程执行完毕。 要注意用户可能分不清阻塞和等待的区别,需要解释清楚。比如,阻塞通常是被动的,比如等待资源;而等待是主动的,比如调用了wait()方法。另外,不同系统可能有不同的状态划分,比如有的将阻塞、等待等合并,有的分开。需要指出这些可能的差异。 还要确保使用正确的中文术语,比如“就绪”、“运行中”等。同时,按照系统级指令,行内公式用$...$,独立公式用$$...$$,但这个问题可能不需要数学公式,所以可能用不上。不过如果有例子涉及到计算,可能需要用,但这里可能不涉及,所以暂时不需要。 需要检查是否有遗漏的状态,比如挂起状态,不过一般主流的线程状态模型中可能不包括挂起,或者属于阻塞的一种。可能不同的资料有不同的分法,所以需要说明常见的几种,并指出不同实现可能有差异。 最后,总结线程状态转换的原因,比如主动调用方法、等待时间到期、I/O完成等。确保用户理解状态之间的转换条件。</think>线程状态反映了其生命周期中的不同阶段。不同编程语言或操作系统的具体实现可能略有差异,但通常包含以下几种核心状态: --- ### **1. 新建状态(New)** - **描述**:线程被创建但尚未启动(未调用 `start()` 方法)。 - **特点**:此时线程未分配系统资源(如CPU时间片),仅完成初始化。 - **转换条件**:调用 `start()` 后进入就绪状态。 --- ### **2. 就绪状态(Runnable)** - **描述**:线程已准备好运行,等待CPU分配时间片。 - **特点**: - 在操作系统中也称为“可运行状态”。 - 可能因时间片用完、主动让出(如`yield()`)或更高优先级线程抢占而暂停。 - **转换条件**: - 获得CPU时间片 → 进入运行状态; - 等待资源(如锁) → 进入阻塞状态。 --- ### **3. 运行状态(Running)** - **描述**:线程正在执行代码,占用CPU资源。 - **特点**: - 在单核CPU中,同一时刻只有一个线程处于此状态; - 在多核/多线程CPU中,多个线程可同时运行。 - **转换条件**: - 时间片用完 → 返回就绪状态; - 主动等待或阻塞 → 进入阻塞/等待状态。 --- ### **4. 阻塞状态(Blocked)** - **描述**:线程因等待外部资源(如I/O操作、锁释放)而暂停执行。 - **特点**: - **被动进入**:不占用CPU资源,需依赖外部条件解除阻塞(如数据到达、锁释放)。 - **典型场景**:竞争同步锁失败(如`synchronized`)、等待I/O完成。 - **转换条件**:资源可用时 → 返回就绪状态。 --- ### **5. 等待状态(Waiting)** - **描述**:线程主动进入等待,需其他线程显式唤醒(如调用 `notify()` 或 `interrupt()`)。 - **特点**: - **主动进入**:通过调用 `wait()`、`join()` 等方法触发; - **无限期等待**:不指定超时时间。 - **转换条件**:被其他线程唤醒 → 返回就绪状态。 --- ### **6. 超时等待状态(Timed Waiting)** - **描述**:线程主动进入等待,但设置了超时时间(如 `sleep(ms)`、`wait(timeout)`)。 - **特点**: - 超时后自动唤醒,无需外部干预; - 典型方法:`Thread.sleep()`、`Object.wait(timeout)`。 - **转换条件**:超时时间到或被唤醒 → 返回就绪状态。 --- ### **7. 终止状态(Terminated)** - **描述**:线程执行完毕或因异常结束。 - **特点**: - 释放所有占用的资源; - 不可再次启动(调用 `start()` 会抛出异常)。 --- ### **状态转换示意图** ```plaintext 新建 → start() → 就绪 ↔ 运行 运行 → 等待资源 → 阻塞 → 资源就绪 → 就绪 运行 → wait() → 等待/超时等待 → notify()/超时 → 就绪 运行 → 结束 → 终止 ``` --- ### **关键区别** 1. **阻塞 vs. 等待**: - 阻塞是被动的(如等待锁),等待是主动的(如调用 `wait()`)。 2. **等待 vs. 超时等待**: - 前者依赖唤醒,后者可自动恢复。 不同系统(如Java线程模型 vs. 操作系统原生线程)可能合并或细分状态,但核心逻辑一致。理解状态转换对调试并发问题(如死锁、资源竞争)至关重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值