文档章节

Java 线程状态之 BLOCKED

国栋
 国栋
发布于 2016/07/05 22:14
字数 1636
阅读 2523
收藏 31

上一篇章中,我们强调了 BLOCKED 状态跟 I/O 的阻塞是不同的,它不是一般意义上的阻塞,而是特指被 synchronized 块阻塞,即是跟线程同步有关的一个状态。

BLOCKED 状态的定义

前面已经说过 BLOCKED(阻塞) 的简单定义为:

一个正在阻塞等待一个监视器锁的线程处于这一状态。(A thread that is blocked waiting for a monitor lock is in this state.)

更加详细的定义可以参考 Thread.State 中的 javadoc:

/**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

这句话很长,可以拆成两个简单句来理解。

  1. A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method。

    一个处于 blocked 状态的线程正在等待一个监视器锁以进入一个同步的块或方法。

  2. A thread in the blocked state is waiting for a monitor lock to reenter a synchronized block/method after calling Object.wait。

    一个处于 blocked 状态的线程正在等待一个监视器锁,在其调用 Object.wait 方法之后,以再次进入一个同步的块或方法。

进入(enter)同步块时阻塞

先说第一句,这个比较好理解。

监视器锁用于同步访问,以达到多线程间的互斥。所以一旦一个线程获取锁进入同步块,在其出来之前,如果其它线程想进入,就会因为获取不到锁而阻塞在同步块之外,这时的状态就是 BLOCKED。

注:这一状态的进入及解除都不受我们控制,当锁可用时,线程即从阻塞状态中恢复。

我们可以用一些代码来演示这一过程:

@Test
public void testBlocked() throws Exception {
    class Counter {
        int counter;
        public synchronized void increase() {
            counter++;
            try {
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    Counter c = new Counter();
    
    Thread t1 = new Thread(new Runnable() {
        public void run() {
            c.increase();
        }
    }, "t1线程");
    t1.start();
    
    Thread t2 = new Thread(new Runnable() {
        public void run() {
            c.increase();
        }
    }, "t2线程");
    t2.start();
    
    Thread.sleep(100); // 确保 t2 run已经得到执行
    assertThat(t2.getState()).isEqualTo(Thread.State.BLOCKED);
}

以上定义了一个访问计数器 counter,有一个同步的 increase 方法。t1 线程先进入,然后在同步块里面睡觉,导致锁迟迟无法释放,t2 尝试执行同步方法时就因无法获取锁而被阻塞了。

VisualVM 监控显示了 t2 线程的状态:(图片看不清可将页面放大至125%,下同)

thread state blocked jvisualvm

图上的“监视(monitor)”状态即为 BLOCKED 状态。可以看到在t1睡眠期间t2处于 BLOCKED 状态。

BLOCKED 状态可以视作是一种特殊的 WAITING,特指等待锁。

wait 之后重进入(reenter)同步块时阻塞

现在再次来看第二句:

2. A thread in the blocked state is waiting for a monitor lock to reenter a synchronized block/method after calling Object.wait。

一个处于 blocked 状态的线程正在等待一个监视器锁,在其调用 Object.wait 方法之后,以再次进入一个同步的块或方法。

这句话有点绕,也不好翻译成一句简洁的中文。如果没有对 wait 的相关背景有较好的理解,则不容易理解这句话。我们在此把它稍微展开讲一下。既然是 reenter,说明有两次 enter,这个过程是这样的:

  1. 调用 wait 方法必须在同步块中,即是要先获取锁并进入同步块,这是第一次 enter。
  2. 而调用 wait 之后则会释放该锁,并进入此锁的等待队列(wait set)中。
  3. 当收到其它线程的 notify 或 notifyAll 通知之后,等待线程并不能立即恢复执行,因为停止的地方是在同步块内,而锁已经释放了,所以它要重新获取锁才能再次进入(reenter)同步块,然后从上次 wait 的地方恢复执行。这是第二次 enter,所以叫 reenter。
  4. 但锁并不会优先给它,该线程还是要与其它线程去竞争锁,这一过程跟 enter 的过程其实是一样的,因此也可能因为锁已经被其它线程据有而导致 BLOCKED。

这一过程就是所谓的 reenter a synchronized block/method after calling Object.wait。

我们也用一段代码来演示这一过程:

@Test
public void testReenterBlocked() throws Exception {
    class Account {
        int amount = 100; // 账户初始100元
        public synchronized void deposit(int cash) { // 存钱
            amount += cash;
            notify();
            try {
                Thread.sleep(30000); // 通知后却暂时不退出
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        public synchronized void withdraw(int cash) { // 取钱
            while (cash > amount) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            amount -= cash;
        }
    }
    Account account = new Account();
    
    Thread withdrawThread = new Thread(new Runnable() {
        public void run() {
            account.withdraw(200);
        }
    }, "取钱线程");
    withdrawThread.start();
    
    Thread.sleep(100); // 确保取钱线程已经得到执行
    
    assertThat(withdrawThread.getState()).isEqualTo(Thread.State.WAITING);
    
    Thread depositThread = new Thread(new Runnable() {
        public void run() {
            account.deposit(100);
        }
    }, "存钱线程");
    Thread.sleep(10000); // 让取钱线程等待一段时间
    depositThread.start();

    Thread.sleep(300); // 确保取钱线程已经被存钱线程所通知到

    assertThat(withdrawThread.getState()).isEqualTo(Thread.State.BLOCKED);
}

简要介绍一下以上代码场景:

  1. 有一个账户对象,有存钱(deposit)和取钱(withdraw)方法,初始金额100元。
  2. 取钱线程先启动,并进入(enter)同步块,试图取200元,发现钱不够,调用 wait,锁释放,线程挂起(WAITING 状态)。
  3. 10秒后存钱线程启动,存入钱并通知(notify)取钱线程,但之后继续在同步块中睡眠,导致锁没有释放。
  4. 取钱线程收到通知后,退出 WAITING 状态,但已经不持有锁,当试图重新进入(reenter)同步块以恢复执行时,因锁尚未被存钱线程释放,于是被阻塞(BLOCKED 状态)。

监控的显示:

thread state reenter blocked jvisualvm

如图,取钱线程先是 WAITING,在收到通知因无法获取锁而阻塞(BLOCKED)。

总结

综合来看这两句话,两层意思,其实还是一个意思,简单地讲,就是enter,reenter 也还是 enter,概括地讲:

当因为获取不到锁而无法进入同步块时,线程处于 BLOCKED 状态。

如果有线程长时间处于 BLOCKED 状态,要考虑是否发生了死锁(deadlock)的状况。

BLOCKED 状态可以视作为一种特殊的 waiting,是传统 waiting 状态的一个细分:

image

由于还没有讲到 WAITING 状态,而这里有涉及到了 wait 方法,所以上面对 wait 也稍微做了些分析,在下一章节,会更加详细的分析 WAITING 和 TIMED_WAITING 这两个状态。

© 著作权归作者所有

共有 人打赏支持
国栋

国栋

粉丝 373
博文 79
码字总数 154046
作品 0
东莞
程序员
私信 提问
加载中

评论(2)

国栋
国栋

引用来自“cyberface”的评论

@国栋 ,好文章,什么时候有 WAITING 部分的文章啊?坐等更新!!
sorry,暂时没有计划
cyberface
cyberface
@国栋 ,好文章,什么时候有 WAITING 部分的文章啊?坐等更新!!
Jstack线程状态BLOCKED/TIMED_WAITING/WAITING解释

原文:https://blog.csdn.net/xionghan01/article/details/52840358 一、线程5种状态 新建状态(New) 新创建了一个线程对象。 就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的...

Tenderrain
2018/06/22
0
0
Core Java基础知识之一(线程私房菜)

Java的高明之处就是在于不断的总结实际应用中不断出现的通用问题,为此提供此类问题的底层实现,向调用者提供解决问题的接口实现,调用者大可不必完全的明白底层的实现原理和实现过程,只要照...

保罗的寓言
2011/05/21
0
0
Java线程Dump分析工具--jstack

jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式: jstack [-l][F] p...

serenity
2015/08/03
0
0
java多线程小结,及解决应用挂死的问题

这两天为了定位JBOSS老是挂死的问题,学习了一下JAVA多线程方面的知识,在此总结一下 1、在Java程序中,JVM负责线程的调度。线程调度是指按照特定的机制为多个线程分配CPU的使用权。 调度的模...

2k10
2015/07/21
0
1
求有经验的Java大牛看看,Tomcat 中System.out.println()造成死锁

这几天有个Java Web 项目,跑着跑着,tomcat就假死了,用Jstack PID 打印堆栈信息,发现大量的线程处于blocked状态。下图就是其中一个blocked线程.其它blocked线程报错内容和下图类似。 我定...

ZhangFinder
2017/04/25
2.1K
14

没有更多内容

加载失败,请刷新页面

加载更多

4.57 MariaDB慢查询日志 4.58 Tomcat_JDK部署 4.59 zrlog安装 4.60 Nginx代理Tomcat

4.57 MariaDB慢查询日志 为什么要配置慢查询日志? 目的是为了帮助我们分析MariaDB的瓶颈点。 如何配置? 1)进入MariaDB里面执行:show variables like 'slow%';show variables li...

Champin
今天
3
0
自动机器学习简述(AutoML)

为什么需要自动机器学习 对于机器学习的新用户而言,使用机器学习算法的一个主要的障碍就是算法的性能受许多的设计决策影响。随着深度学习的流行,工程师需要选择相应的神经网络架构,训练过...

naughty
今天
2
0
Android Studio Unable to resolve dependency for错误的排查

记录一次Android Studio Unable to resolve dependency for错误的排查 Android Studio 3.2.1 错误提示 Unable to resolve dependency for... 原因:在gradle中设置的代理并没有gradle 4.6的版......

Gemini-Lin
今天
0
0
java常用设计模式

设计模式; 一个程序员对设计模式的理解: “不懂”为什么要把很简单的东西搞得那么复杂。后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精髓所在,我所理解的“...

呵呵哒灬
今天
5
0
Kafka入门

1、Kafka使用背景 在我们大量使用分布式数据库、分布式计算集群的时候,是否会遇到这样的一些问题: 我们想分析下用户行为(pageviews),以便我们设计出更好的广告位 我想对用户的搜索关键词...

watermelon11
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部