Java 线程状态---WAITING(部分转载)

看到一篇关于写线程waiting状态的文章,感觉很生动有趣,转过来保存下。

总结:

waiting这个状态,就是等待,明确了等待,就不会抢资源了。

一个线程A在拿到锁但不满足执行条件的时候,需要另一个线程B去满足这个条件,
那么线程A就会释放锁并处于waiting的状态,等线程B执行完再执行。
waiting状态的好处是:此状态的线程不再活动,不再参与调度,因此不会浪费 CPU 资源,也不会去竞争锁了,相比暴力的blocking状态,要优雅很多。

进入waiting的方法:

调用wait方法,就能让线程进入waiting状态,notify/notify能让线程结束waiting状态。
join也能让线程进入waiting状态,但也算是特殊的wait

还有个Time_waiting的状态

就是wait方法加了个时间的入参,代表等多久就不等了,避免永远等下去。

blocking和waiting

blocking状态,硬说也算waiting的特殊情况
如果waiting状态的条件是有没有锁,那就可以勉强理解为blocking状态吧。
blocking是线程被动阻塞,waiting是线程主动阻塞,本质上其实是一样的。

下文这个火车上抢厕所的例子,很形象。

以下是转载正文(末尾附原文链接):

image

定义

一个正在无限期等待另一个线程执行一个特别的动作的线程处于这一状态。

A thread that is waiting indefinitely for another thread to perform a particular action is in this state.

然而这里并没有详细说明这个“特别的动作”到底是什么,详细定义还是看 javadoc(jdk8):

一个线程进入 WAITING 状态是因为调用了以下方法:

  • 不带时限的 Object.wait 方法
  • 不带时限的 Thread.join 方法
  • LockSupport.park

然后会等其它线程执行一个特别的动作,比如:

  • 一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的 Object.notify() 或 Object.notifyAll()。
  • 一个调用了 Thread.join 方法的线程会等待指定的线程结束。

     

对应的英文原文如下:

A thread is in the waiting state due to calling one of the following methods:

  • Object.wait with no timeout
  • Thread.join with no timeout
  • LockSupport.park

A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.

线程间的协作(cooperate)机制

显然,WAITING 状态所涉及的不是一个线程的独角戏,相反,它涉及多个线程,具体地讲,这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。

就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。

wait/notify 就是线程间的一种协作机制,那么首先,为什么 wait?什么时候 wait?它为什么要等其它线程执行“特别的动作”?它到底解决了什么问题?

wait 的场景

首先,为什么要 wait 呢?简单讲,是因为条件(condition)不满足。那么什么是条件呢?为方便理解,我们设想一个场景:

有一节列车车厢,有很多乘客,每个乘客相当于一个线程;里面有个厕所,这是一个公共资源,且一次只允许一个线程进去访问(毕竟没人希望在上厕所期间还与他人共享~)。

image

竞争关系

假如有多个乘客想同时上厕所,那么这里首先存在的是竞争的关系。

如果将厕所视为一个对象,它有一把锁,想上厕所的乘客线程需要先获取到锁,然后才能进入厕所。

image

Java 在语言级直接提供了同步的机制,也即是 synchronized 关键字:

synchronized(expression) {……}

它的机制是这样的:对表达式(expresssion)求值(值的类型须是引用类型(reference type)),获取它所代表的对象,然后尝试获取这个对象的锁:

  • 如果能获取锁,则进入同步块执行,执行完后退出同步块,并归还对象的锁(异常退出也会归还);
  • 如果不能获取锁,则阻塞在这里,直到能够获取锁。

在一个线程还在厕所期间,其它同时想上厕所的线程被阻塞,处在该厕所对象的 entry set 中,处于 BLOCKED 状态。

image

完事之后,退出厕所,归还锁。

image

之后,系统再在 entry set 中挑选一个线程,将锁给到它。

image

对于以上过程,以下为一个 gif 动图演示:

当然,这就是我们所熟悉的锁的竞争过程。以下为演示的代码:

@Test
public void testBlockedState() throws Exception {class Toilet { // 厕所类public void pee() { // 尿尿方法try {Thread.sleep(21000);// 研究表明,动物无论大小尿尿时间都在21秒左右} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}
Toilet toilet = <span class="hljs-keyword">new</span> Toilet();Thread passenger1 = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {toilet.pee();}}
});Thread passenger2 = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {toilet.pee();}}
});passenger1.start();<span class="hljs-comment">// 确保乘客1先启动</span>
Thread.sleep(<span class="hljs-number">100</span>);passenger2.start();<span class="hljs-comment">// 确保已经执行了 run 方法</span>
Thread.sleep(<span class="hljs-number">100</span>);<span class="hljs-comment">// 在乘客1在厕所期间,乘客2处于 BLOCKED 状态</span>
assertThat(passenger2.getState()).isEqualTo(Thread.State.BLOCKED);

}

条件

现在,假设有个女乘客,她抢到了锁,进去之后裤子脱了一半,发现马桶的垫圈纸没了,于是拒绝尿。

或许是因为她比较讲究卫生,怕直接坐上去会弄脏她白花花的屁股~

现在,条件出现了:有纸没纸,这就是某种条件。

image

那么,现在条件不满足,这位女线程改怎么办呢?如果只是在里面干等,显然是不行的。

这不就是人民群众所深恶痛绝的“占着茅坑不拉尿”吗?

  • 一方面,外面 entry set 中可能好多群众还嗷嗷待尿呢(其中可能有很多大老爷线程,他们才不在乎有没有马桶垫圈纸~)
  • 另一方面,假定外面同时有“乘务员线程”,准备进去增加垫圈纸,可你在里面霸占着不出来,别人也没法进去,也就没法加纸。

所以,当条件不满足时,需要出来,要把锁还回去,以使得诸如“乘务员线程”的能进去增加纸张。

等待是必要的吗?

那么出来之后是否一定需要等待呢?当然也未必。

这里所谓“等待”,指的是使线程处于不再活动的状态,即是从调度队列中剔除。

如果不等待,只是简单归还锁,用一个反复的循环来判断条件是否满足,那么还是可以再次回到调度队列,然后期待在下一次被调度到的时候,可能条件已经发生变化:

比如某个“乘务员线程”已经在之前被调度并增加了里面的垫圈纸。自然,也可能再次调度到的时候,条件依旧是不满足的。

现在让我们考虑一种比较极端的情况:厕所外一大堆的“女乘客线程”想进去方便,同时还有一个焦急的“乘务员线程”想进去增加厕纸。

image

如果线程都不等待,而厕所又是一个公共资源,无法并发访问。调度器每次挑一个线程进去,挑中“乘务员线程”的几率反而降低了,entry set 中很可能越聚越多无法完成方便的“女乘客线程”,“乘务员线程”被选中执行的几率越发下降。

当然,同步机制会防止产生所谓的“饥饿(starvation)”现象,“乘务员线程”最终还是有机会执行的,只是系统运行的效率下降了。

所以,这会干扰正常工作的线程,挤占了资源,反而影响了自身条件的满足。另外,“乘务员线程”可能这段时间根本没有启动,此时,不愿等待的“女乘客线程”不过是徒劳地进进出出,占用了 CPU 资源却没有办成正事。

效果上还是在这种没有进展的进进出出中等待,这种情形类似于所谓的忙等待 (busy waiting)

协作关系

综上,等待还是有必要的,我们需要一种更高效的机制,也即是 wait/notify 的协作机制。

当条件不满足时,应该调用 wait()方法,这时线程释放锁,并进入所谓的 wait set 中,具体的讲,是进入这个厕所对象的 wait set 中:

image

这时,线程不再活动,不再参与调度,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。

现在的问题是:她们什么时候才能再次活动呢?显然,最佳的时机是当条件满足的时候。

之后,“乘务员线程”进去增加厕纸,当然,此时,它也不能只是简单加完厕纸就完了,它还要执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的女乘客线程:

大概就是向她们喊一声:“有纸啦!赶紧去尿吧!”显然,如果只是“女乘客线程”方面一厢情愿地等待,她们将没有机会再执行。

所谓“通知”,也即是把她们从 wait set 中释放出来,重新进入到调度队列(ready queue)中。

  • 如果是 notify,则选取所通知对象的 wait set 中的一个线程释放;
  • 如果是 notifyAll,则释放所通知对象的 wait set 上的全部线程。

整个过程如下图所示:

image

对于上述过程,我们也给出以下 gif 动图演示:

注意:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为她当初中断的地方是在同步块内,而此刻她已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。(这也即是所谓的 “reenter after calling Object.wait”,在上一个篇章中也曾详细的讨论了这一过程。)

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
  • 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态。

综上,这是一个协作机制,“女乘客线程”和“乘务员线程”间存在一个协作关系。显然,这种协作关系的存在,“女乘客线程”可以避免在条件不满足时的盲目尝试,也为“乘务员线程”的顺利执行腾出了资源;同时,在条件满足时,又能及时得到通知。协作关系的存在使得彼此都能受益。

生产者与消费者问题

不难发现,以上实质上也就是经典的“生产者与消费者”的问题:

乘务员线程生产厕纸,女乘客线程消费厕纸。当厕纸没有时(条件不满足),女乘客线程等待,乘务员线程添加厕纸(使条件满足),并通知女乘客线程(解除她们的等待状态)。接下来,女乘客线程能否进一步执行则取决于锁的获取情况。

代码的演示:

在以下代码中,演示了上述的 wait/notify 的过程:

@Test
public void testWaitingState() throws Exception {
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Toilet</span> </span>{ <span class="hljs-comment">// 厕所类</span><span class="hljs-keyword">int</span> paperCount = <span class="hljs-number">0</span>; <span class="hljs-comment">// 纸张</span><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">pee</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">// 尿尿方法</span><span class="hljs-keyword">try</span> {Thread.sleep(<span class="hljs-number">21000</span>);<span class="hljs-comment">// 研究表明,动物无论大小尿尿时间都在21秒左右</span>} <span class="hljs-keyword">catch</span> (InterruptedException e) {Thread.currentThread().interrupt();}}
}Toilet toilet = <span class="hljs-keyword">new</span> Toilet();<span class="hljs-comment">// 两乘客线程</span>
Thread[] passengers = <span class="hljs-keyword">new</span> Thread[<span class="hljs-number">2</span>];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; passengers.length; i++) {passengers[i] = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {<span class="hljs-keyword">while</span> (toilet.paperCount &lt; <span class="hljs-number">1</span>) {<span class="hljs-keyword">try</span> {toilet.wait(); <span class="hljs-comment">// 条件不满足,等待</span>} <span class="hljs-keyword">catch</span> (InterruptedException e) {Thread.currentThread().interrupt();}}toilet.paperCount--; <span class="hljs-comment">// 使用一张纸</span>toilet.pee();}}});
}<span class="hljs-comment">// 乘务员线程</span>
Thread steward = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {toilet.paperCount += <span class="hljs-number">10</span>;<span class="hljs-comment">// 增加十张纸</span>toilet.notifyAll();<span class="hljs-comment">// 通知所有在此对象上等待的线程</span>}}
});passengers[<span class="hljs-number">0</span>].start();
passengers[<span class="hljs-number">1</span>].start();<span class="hljs-comment">// 确保已经执行了 run 方法</span>
Thread.sleep(<span class="hljs-number">100</span>);<span class="hljs-comment">// 没有纸,两线程均进入等待状态</span>
assertThat(passengers[<span class="hljs-number">0</span>].getState()).isEqualTo(Thread.State.WAITING);
assertThat(passengers[<span class="hljs-number">1</span>].getState()).isEqualTo(Thread.State.WAITING);<span class="hljs-comment">// 乘务员线程启动,救星来了</span>
steward.start();<span class="hljs-comment">// 确保已经增加纸张并已通知</span>
Thread.sleep(<span class="hljs-number">100</span>);<span class="hljs-comment">// 其中之一会得到锁,并执行 pee,但无法确定是哪个,所以用 "或 ||"</span>
<span class="hljs-comment">// 注:因为 pee 方法中实际调用是 sleep, 所以很快就从 RUNNABLE 转入 TIMED_WAITING(sleep 时对应的状态)</span>
assertTrue(Thread.State.TIMED_WAITING.equals(passengers[<span class="hljs-number">0</span>].getState())|| Thread.State.TIMED_WAITING.equals(passengers[<span class="hljs-number">1</span>].getState()));<span class="hljs-comment">// 其中之一则被阻塞,但无法确定是哪个,所以用 "或 ||"</span>
assertTrue(Thread.State.BLOCKED.equals(passengers[<span class="hljs-number">0</span>].getState()) || Thread.State.BLOCKED.equals(passengers[<span class="hljs-number">1</span>].getState()));

}

join 的场景及其它

从定义中可知,除了 wait/notify 外,调用 join 方法也会让线程处于 WAITING 状态。

join 的机制中并没有显式的 wait/notify 的调用,但可以视作是一种特殊的,隐式的 wait/notify 机制。

假如有 a,b 两个线程,在 a 线程中执行 b.join(),相当于让 a 去等待 b,此时 a 停止执行,等 b 执行完了,系统内部会隐式地通知 a,使 a 解除等待状态,恢复执行。

换言之,a 等待的条件是 “b 执行完毕”,b 完成后,系统会自动通知 a。

关于 LockSupport.park 的情况则由读者自行分析。

与传统 waiting 状态的关系

Thread.State.WAITING 状态与传统的 waiting 状态类似:

image

本文后面部分转载自:https://my.oschina.net/goldenshaw/blog/802620

如有侵权,请告知删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499822.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[记录] --- linux上项目

1.安装jdk8 yum -y list java* 查看可安装java版本 yum install -y java-1.8.0-openjdk-devel.x86_64 java -version jdk在/usr/lib/jvm目录 2.配置环境变量 打开/etc/profile 在profile文件末尾加入: export JAVA_HOME/usr/lib/jvm/java-1.8.0 export PATH$JAVA_HOME/bin:…

[记录] --- linux安装redis

获取redis资源 wget http://download.redis.io/releases/redis-具体版本.tar.gz 解压 tar xzvf redis-具体版本.tar.gz 安装 cd redis-4.0.8 make&#xff08;这个需要安装了gcc,没有gcc的先执行这个命令:yum install gcc&#xff09; cd src mkdir /usr/local/redis/etc&…

服务端高并发分布式架构演进之路(转载,图画的好)

这个文章基本上从单机版到最终版&#xff0c;经历了加缓存&#xff0c;加机器&#xff0c;高可用&#xff0c;分布式&#xff0c;最后到云等过程&#xff0c;其实我一直想总结一套类似的东西&#xff0c;没想到有人已经先弄出来了&#xff0c;那就不重复造轮子了&#xff0c;而…

java 记录一个类加载顺序的坑

看看这个&#xff0c;先想想结果打印的顺序和a、b的值 public class ClassLoderTest {public static void main(String[] args) {staticFun();}static ClassLoderTest clt new ClassLoderTest();static {System.out.println(1);}{System.out.println(2);}ClassLoderTest(){Sy…

[前端记录] --- vue axios 等调用完再执行后面的语句

vue 整合echarts的时候&#xff0c;是先axios 调用后台接口&#xff0c;获取数据&#xff0c;再将数据渲染到页面。 一开始的问题是&#xff1a;第一次请求完&#xff0c;页面没数据&#xff0c;再请求一次才出现数据 分析到最后&#xff0c;发下是由于axios是异步请求的&…

docker容器核心技术点

docker容器核心技术点&#xff1a; 容器其实本质上就是一个进程&#xff0c;只不过容器的进程是比较特殊的。 容器技术的核心功能&#xff0c;就是通过约束和修改进程的动态表现&#xff0c;创造出一个“边界”&#xff0c;通过“障眼法”让人觉得它是一个独立的系统。大多数容…

spring-boot 一款无侵入型,轻量级的接口文档生成工具apiggs

spring-boot 一款无侵入型&#xff0c;轻量级的接口文档生成工具apiggs 名字叫&#xff1a;apiggs 使用只需两步&#xff0c;先把插件代码引入pom文件&#xff0c;然后编译打包即可 之后项目的target目录下&#xff0c;就会多出一个apiggs的文件夹&#xff0c;里面便是项目cont…

亚马逊S3文件存储的可视化

windos图形化工具下载&#xff1a;http://s3browser.com/ 类似于用xftp操作linux文件一样方便直观清晰

为什么CAP不能同时满足的简单理解

为什么CAP不能同时满足的简单理解 cap定理&#xff1a;前提是分布式系统中&#xff0c;cap三个只能满足两个&#xff0c;不能同时满足三个特性。 Consistency (一致性)&#xff1a;所有节点在同一时间的数据都是完全一致的。 Availability (可用性)&#xff1a;服务一直是可用…

Spring的@Scheduled 动态更新cron表达式

常见的本地定时写法如下&#xff1a; Scheduled(cron "0/5 * * * * ?")private void test() {log.info("业务处理逻辑。。。5秒一次");}如果想要动态更新cron表达式&#xff0c;可以这样写&#xff1a; 先写一个类&#xff0c;让cron表达式总是读成员变量…

spring boot 整合 谷歌guava的EventBus 实现单机版的消息发布订阅

spring boot 整合 谷歌guava的EventBus 实现单机版的消息发布订阅 大型分布式系统&#xff0c;直接用mq解耦&#xff0c;那么单机系统怎么办&#xff0c;可以考虑用EventBus 用EventBus的好处也是异步解耦&#xff0c;和mq的类似&#xff0c;可以勉强认为是单机版的mq 先解释…

我们的java项目,要不要废弃switch

java项目中要不要废弃使用switch 先看switch是怎么产生的&#xff1a; 很久以前&#xff0c;计算能力很昂贵&#xff0c;电脑性能很差&#xff0c;人们便想着法子的提高执行效率 先看看if(test0)的处理逻辑&#xff1a; 先是把test的值放在一个寄存器中&#xff0c;然后把0放…

限流算法(漏桶算法、令牌桶算法)对比

限流算法&#xff08;漏桶算法、令牌桶算法&#xff09; 漏桶算法&#xff1a; 有个桶&#xff0c;比如最大能进2个单位的水&#xff08;请求&#xff09;&#xff0c;桶底有个洞&#xff0c;每个单位的水都会在桶里待3秒后漏下去。 那么这个桶就可以同时处理2个单位的水。 如…

guns框架

分享一个框架guns https://gitee.com/naan1993/guns/ 这算是国内比较优秀的框架&#xff0c;简单的套路都有。 权限啊&#xff0c;代码自动生成啊等等 快速搭建一套后台管理项目 下面是作者介绍&#xff1a; Guns基于Spring Boot2&#xff0c;致力于做更简洁的后台管理系统。…

URLDecoder: Illegal hex characters in escape (%) pattern ...

URL中含有%&#xff0c;报错如下&#xff1a; URLDecoder: Illegal hex characters in escape (%) pattern … 解决&#xff1a; uriStr uriStr.replaceAll("%","%25"); 这种情况一般是出现在连接mongoDB数据库的时候&#xff0c;因为要把用户名密码写…

[设计模式] ------ 策略模式实战:java中替代if-else的大段逻辑

java中用策略模式替代if-else的大段逻辑 问题&#xff1a; java本来是一门以面向对象为主的语言&#xff0c;但很多人嘴上说着java面向对象&#xff0c;然后除了表映射实体之外&#xff0c;其他的还是面向过程的思路。 就比如今天要说的&#xff0c;代码中大段大段的if-else判…

mongodb 索引详解

使用springboot连接mongodb的时候&#xff0c;涉及到索引的使用 举例&#xff1a; Document(collection"book") //注释的是复合索引 //CompoundIndexes( // { // CompoundIndex(name "复合索引名字",def "{字段01:1,字段02:…

[转载] --- 让线程按顺序执行8种方法

看到一篇比较用心的总结&#xff0c;涉及到很多知识点&#xff0c;转来保存&#xff0c;而且我把里面的每个方法都试了一遍&#xff0c;亲测没问题 此次转载&#xff0c;还新增了一些说明和结构 我的总结&#xff1a; 其实&#xff0c;让线程按顺序执行&#xff0c;其实就是…

mongodb数据库,批量插入性能测试记录

spring boot 框架下&#xff0c;操作mongodb数据库 maven&#xff1a;spring-data-mongodb:2.1.3.RELEASE mongo数据库用的是本地的mongo&#xff0c;所以环境不一样&#xff0c;可能结果不一样。但趋势应该是一样的。 测试保证每次批量插入时&#xff0c;库里的数据量都是一…