等待(WAIT)和阻塞(BLOCKED)线程状态之间的区别

143

线程状态WAIT和线程状态BLOCKED之间有什么区别?

Thread.State文档

BLOCKED(阻塞)
在等待获取监视器锁的线程处于此状态。

WAITING(等待)
无限期地等待另一个线程执行特定操作的线程处于此状态。

这并没有为我解释清楚它们之间的区别。


请在此线程中检查答案:https://dev59.com/3HE85IYBdhLWcg3w8IXK,此链接可能提供进一步的澄清:http://geekexplains.blogspot.cz/2008/07/threadstate-in-java-blocked-vs-waiting.html。 - Abdul
1
@Abdul 这个GeekExplains上的链接说一个线程可以通过调用Object.wait()进入阻塞状态,这不对吧? - More Than Five
1
根据Oracle文档http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.State.html#WAITING:由于调用以下方法之一,线程处于等待状态:Object.wait没有超时,Thread.join没有超时,LockSupport.park。 - Abdul
1
就记录而言,我认为@Flavio的答案比Ankit的好一点,如果你考虑更改的话。 - Gray
6个回答

123
一个线程一旦在对象上调用wait()方法,就会进入等待状态,这被称为等待状态。 一旦一个线程到达等待状态,它将需要等待直到另一个线程在该对象上调用 notify()notifyAll() 方法。
当该线程得到通知时,它将不会是可运行的。可能其他线程也被通知(使用notifyAll()),或者第一个线程尚未完成工作,因此仍然被阻塞,直到获得机会。 这被称为阻塞状态。 当一个线程试图获取对象上的锁,并且某些其他线程已经持有锁时,将会发生阻塞状态。
一旦其他线程离开并留下机会给该线程,它将转移到可运行状态,然后根据JVM线程机制选择执行工作并进入运行状态。

3
你解释得更好是因为你解释了线程达到这两种状态的顺序,这比仅仅在孤立情况下解释这两种状态更清晰(这是“More Than Five”的回答所做的)。 - Kumar Manish
9
对于那些想知道为什么网络上找到的大部分(或全部)状态图表声称notify()/notifyAll()会导致线程状态从BLOCKED变成RUNNABLE的人们,请参考此链接:https://dev59.com/q14c5IYBdhLWcg3wD2jC - Niklas Peter
假设只有一个线程并等待了一段时间(以毫秒为单位);现在是否可能一个线程可以直接从等待状态转换到可运行状态?因为这里只有单线程,没有其他线程获取锁。 - Kanagavelu Sugumar
有一个wait(time)方法,一旦时间过去,它将返回可运行状态。但是如果没有指定时间,它将等待其他线程通知或线程被中断。 - Ankit Bansal
2
你的回答很好,但是它并没有完全解释当你尝试获取锁时,你可以随时进入阻塞状态。这与信号/通知无关。 - Gray

108

区别相对简单。

BLOCKED 状态下,线程即将进入一个 synchronized 块,但是另一个线程正在同一个对象上运行一个 synchronized 块。第一个线程必须等待第二个线程退出其块。

WAITING 状态下,线程正在等待来自另一个线程的信号。这通常通过调用 Object.wait()Thread.join() 实现。线程将保持在此状态直到另一个线程调用 Object.notify() 或死亡。


2
只有线程本身才能使其进入等待状态,这种说法正确吗?线程B能否使线程A进入等待状态? - More Than Five
1
你很少直接使用 Object.wait(),但是当你使用更高级的并发构造 - 如锁、阻塞队列等时,你最终会进入 WAITING 状态... 广义地说,每当两个线程需要协调时。 - Flavio
1
从个人经验来看,等待IO(例如从Socket读取)的线程处于“运行”状态。 - Flavio
4
Java8文档中对于Thread.State的说明是,“……这些状态是虚拟机状态,不反映任何操作系统线程状态。” 换句话说,JVM不关心正在运行Java代码的线程、等待系统调用返回的线程或等待时间片的线程之间的区别。在JVM看来,它们都只是RUNNABLE - Solomon Slow
5
当一个线程从WAITING状态切换时,需要先进入BLOCKED状态直到获取与其等待的对象相关联的锁之后,才能继续执行。这段话可以加上一句话,指出这个过程中的细节。 - Gray
显示剩余2条评论

44
阻塞状态和等待状态的重要区别在于它们对调度程序的影响。处于阻塞状态的线程正在争夺一个锁,因此该线程仍然被视为调度程序需要服务的对象,可能会影响调度程序决定给运行线程多少时间(以便让正在等待锁的线程有机会运行)。
一旦线程处于等待状态,它所产生的系统负载就被最小化了,调度程序也不必担心它。线程会进入休眠状态直到收到通知。除了占用一个操作系统线程外,它完全处于非活跃状态。
这就是为什么使用notifyAll不太理想的原因,它会唤醒以前快乐地休眠并没有对系统造成负载的一堆线程,其中大部分线程将阻塞,直到它们获取锁、发现等待的条件不成立并返回等待状态。最好只通知那些有机会取得进展的线程。
(使用ReentrantLock而不是内部锁允许您为一个锁设置多个条件,以确保被通知的线程是正在等待特定条件的线程,避免线程因收到无法处理的通知而丢失信号的问题。)

这是因为其他线程有责任在监视器对象上调用notify()吗? - berimbolo
1
@berimbolo:我不明白你在问什么。 - Nathan Hughes
这是关于为什么等待线程不是调度程序需要担心的问题。我想知道这是否是因为另一个线程将负责在等待时调用通知。 - berimbolo
@berimbolo:等待线程最终会被notify唤醒。调度程序将决定哪个等待线程被通知。 - Nathan Hughes
2
如果应该避免这种行为,则应使用notify。但是,仅供一些人了解,他们认为notify始终是更好的选择。如果您的应用程序可能会出现死锁类似的情况(如果发生这种情况,您的应用程序将无法按照您的意图工作),则应使用notifyAll,以避免“错过信号”(在这里,预期的线程错过了信号,因为信号到达了其他线程而不是预期的线程)。请参见https://dev59.com/_nVD5IYBdhLWcg3wQZQg#3186336。 - user104309
显示剩余4条评论

29

解读线程转储的简化视角:

  • WAIT - 我正在等待被分配工作,所以现在我是空闲的。
  • BLOCKED - 我正忙于尝试完成工作,但另一个线程挡住了我的路,所以现在我是空闲的。
  • RUNNABLE...(Native Method) - 我调用了一些本地代码(尚未完成),因此就JVM而言,你是RUNNABLE,它无法提供任何进一步的信息。

一个常见的例子是使用C编写的本地套接字监听器方法,实际上正在等待任何流量到达,所以我现在是空闲的。在这种情况下,这可以看作是一种特殊的WAIT,因为我们实际上根本没有RUNNING(没有CPU燃烧),但您必须使用操作系统线程转储而不是Java线程转储来查看它。


1
我喜欢你的解释。那正是我现在在分析线程转储时所尝试做的 :) - Sridhar Sarnobat
@MuhammadGelbana 是的,你说得对,我已经删除了评论。 - Eric
1
你的RUNNABLE还不太对。它可能在Java运行队列中,但没有执行,也可能正在执行Java代码。它不一定要调用本地代码。 - Gray

3

被阻塞-您的线程处于可运行的状态,试图获取对象锁。 等待-您的线程处于等待状态,等待通知信号进入可运行线程的状态。


-2

看这个例子:

线程状态的演示。

/*NEW- thread object created, but not started.
RUNNABLE- thread is executing.
BLOCKED- waiting for monitor after calling wait() method.
WAITING- when wait() if called & waiting for notify() to be called.
  Also when join() is called.
TIMED_WAITING- when below methods are called:
 Thread.sleep
 Object.wait with timeout
 Thread.join with timeout
TERMINATED- thread returned from run() method.*/
public class ThreadBlockingState{

public static void main(String[] args) throws InterruptedException {
    Object obj= new Object();
    Object obj2 = new Object();
    Thread3 t3 = new Thread3(obj,obj2);
    Thread.sleep(1000);
    System.out.println("nm:"+t3.getName()+",state:"+t3.getState().toString()+
            ",when Wait() is called & waiting for notify() to be called.");
    Thread4 t4 = new Thread4(obj,obj2);
    Thread.sleep(3000);
    System.out.println("nm:"+t3.getName()+",state:"+t3.getState().toString()+",After calling Wait() & waiting for monitor of obj2.");
    System.out.println("nm:"+t4.getName()+",state:"+t4.getState().toString()+",when sleep() is called.");
}

}
class Thread3 extends Thread{
Object obj,obj2;
int cnt;
Thread3(Object obj,Object obj2){
    this.obj = obj;
    this.obj2 = obj2;
    this.start();
}

@Override
public void run() {
    super.run();
    synchronized (obj) {
        try {
            System.out.println("nm:"+this.getName()+",state:"+this.getState().toString()+",Before Wait().");
            obj.wait();             
            System.out.println("nm:"+this.getName()+",state:"+this.getState().toString()+",After Wait().");
            synchronized (obj2) {
                cnt++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
}
class Thread4 extends Thread{
Object obj,obj2;
Thread4(Object obj,Object obj2){
    this.obj = obj;
    this.obj2 = obj2;
    this.start();
}

@Override
public void run() {
    super.run();
    synchronized (obj) {
        System.out.println("nm:"+this.getName()+",state:"+this.getState().toString()+",Before notify().");
        obj.notify();
        System.out.println("nm:"+this.getName()+",state:"+this.getState().toString()+",After notify().");
    }
    synchronized (obj2) {
        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
}

1
谢谢你提供代码,但我宁愿您给出文字答案,然后展示一个_小的_代码块。 - Gray
如果代码非常清晰易懂,那么推理起来会非常有用。 - darw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接