多线程之block
2021/10/13 6:15:49
本文主要是介绍多线程之block,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Block和IO阻塞
1、简单介绍
BLOCKED 状态跟 I/O 的阻塞是不同的,BLOCKED不是一般意义上的阻塞,而是特指被 synchronized 块阻塞,即是跟线程同步有关的一个状态。
2、BLOCK状态定义
一个正在阻塞等待一个监视器锁的线程处于这一状态。(A thread that is blocked waiting for a monitor lock is in this state.)
java类中的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,
进入(enter)同步块时阻塞
这句话很长,可以拆成两个简单句来理解。
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 方法之后,想要再次进入一个同步的块或方法。
先说第一句,这个比较好理解。监视器锁用于同步访问,以达到多线程间的互斥。所以一旦一个线程获取锁进入同步块,在其出来之前,如果其它线程想进入,就会因为获取不到锁而阻塞在同步块之外,这时的状态就是 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 线程的状态:
从上面的红色状态可以看到t2线程是在监视中,而t1线程正在休眠中。在23:46:20到26:46:35之间,t1线程处于休眠阶段;
t1线程在23:46:35之后,处于空白阶段,也就是运行结束;而t2开始了睡眠。
图上的“监视(monitor)”状态即为 BLOCKED 状态。可以看到在t1睡眠期间t2处于 BLOCKED 状态。
BLOCKED 状态可以视作是一种特殊的 WAITING,特指等待锁。
wait 之后重进入(reenter)同步块时阻塞
现在再次来看第二句:
- 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。
简要介绍一下代码场景:
- 有一个账户对象,有存钱(deposit)和取钱(withdraw)方法,初始金额100元。
- 取钱线程先启动,并进入(enter)同步块,试图取200元,发现钱不够,调用 wait,锁释放,线程挂起(WAITING 状态)。
- 10秒后存钱线程启动,存入钱并通知(notify)取钱线程,但之后继续在同步块中睡眠,导致锁没有释放。
- 取钱线程收到通知后,退出 WAITING 状态,但已经不持有锁,当试图重新进入(reenter)同步块以恢复执行时,因锁尚未被存钱线程释放,于是被阻塞(BLOCKED 状态)。
也用一段代码来演示这一过程:
public static void main(String[] args) throws InterruptedException { 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() { @Override public void run() { account.withdraw(200); } }, "取钱线程"); withdrawThread.start(); try { Thread.sleep(100); // 确保取钱线程已经得到执行 log.info("二者的线程状态是:{}",withdrawThread.getState()==Thread.State.WAITING); } catch (InterruptedException e) { e.printStackTrace(); } Thread depositThread = new Thread(new Runnable() { @Override public void run() { account.deposit(100); } }, "存钱线程"); Thread.sleep(10000); // 让取钱线程等待一段时间 depositThread.start(); Thread.sleep(300); // 确保取钱线程已经被存钱线程所通知到 log.info("二者的线程状态是:{}",withdrawThread.getState()==Thread.State.WAITING); }
运行上面的代码,然后查看图片:
从上图上看,取钱线程和存钱线程
开始是取钱线程先执行,然后进入到wait方法中去,陷入了waiting状态,然后存钱线程开始执行后,通知取钱线程后,陷入了休眠状态,但是未释放锁,而取钱线程从wait状态中解脱,进入到block状态,取钱线程一直在等待得到锁的释放,得到锁后马上来进行执行。
所以:取钱线程先是 WAITING,在收到通知因无法获取锁而阻塞(BLOCKED)。
如果有线程长时间处于 BLOCKED 状态,要考虑是否发生了死锁(deadlock)的状况。
BLOCKED 状态可以视作为一种特殊的 waiting,是传统 waiting 状态的一个细分:
由于还没有讲到 WAITING 状态,而这里有涉及到了 wait 方法,所以上面对 wait 也稍微做了些分析,在下一章节,会更加详细的分析 WAITING 和 TIMED_WAITING 这两个状态。
这篇关于多线程之block的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享