转载

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

背景介绍一下

作为一名刚步入社会的小菜鸡程序猿,不就是应该要做一个上班努力工作,下班好好充电的崽崽吗?既然想要好好充电,那肯定避免不了询问大神的建议,看看有什么好书是适合我这种菜鸡好好通读的。所以老老实实的去技术群问问。

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

以我多年在技术群潜水得出的经验,大佬们只有在教导新人以及吹水的时候格外积极。所以

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

我华丽的进化为一个什么都不懂的萌新(虽然本来就是菜鸡),让大佬们推荐几本有关JAVA的推荐读本。在和大佬的斗智斗勇并清除了一些什么《JAVA从入门到入土》、《Mysql从入门到入狱》等等之类的书籍。敲定下来买了以下这几本书,其中就有《JAVA并发编程的艺术》

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

拿到新书,迫不及待的想了解《JAVA并发编程的艺术》到底是什么地方让无数人折服。

翻开这本书

上下文切换,嗯懂了。

如何减少上下文切换,嗯懂了。

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

(前面的内容也太简单了吧)

当我读到1.1.4:减少上下文切换实战(我果然还是太年轻了啊)

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

嗯?通过减少线程池中大量WAITING的线程会减少上下文切换的次数?

等等,减少大量的WAITING状态的线程是会减少创建时间的开销,这个我承认,但是你为什么会影响到上下文切换的次数呢?作者大大这里是不是写错了?

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

场景复现

这里就涉及到命令行操作了。具体的我就不贴出来了,这里大体的讲一下作者大大的思路。

1、作者大大通过命令行发现了大量的闲置线程,然后发现闲置线程是线程池中的工作线程(这里说明线程池接受的任务较少,大量线程闲置),然后找到配置文件减少了线程的最大数量,然后重启线程池。

2、WAITING线程减少了同时系统上下文切换的次数就会少,因为每一次从WAITING到RUNNABLE都会进行一次上下文切换。

所以得出结论:通过减少大量WAITING的线程,可以减少上下文切换次数。

疑惑来了

这里我就有一个疑惑:

为什么WAITING的线程,可以减少上下文切换次数?它不是应该乖乖的呆在线程池里面等待,什么都不干老老实实当一个咸鱼嘛?

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

在我的理解中线程在等待某个条件引起了休眠,当条件满足就有可能引起调度,或者某些可中断的睡眠过程中收到信号也可能发生调度,如果两个条件都不满足 线程就会一直睡下去,不会调度就不会引起上下文切换。

所以大量线程处于 waiting 状态而请求数又少的话,那么OS从300个waiting线程中选一个处理请求跟从150个 waiting 线程中选一个不都是只有1次上下文切换嘛?

举个例子来说新到20个请求,线程在等待某个条件引起了休眠,当条件满足就有可能引起调度,或者某些可中断的睡眠过程中收到信号也可能发生调度,

如果两个条件都不满足 线程就会一直睡下去,不会调度就不会引起上下文切换,那么无论你处于 waiting 的线程有多少,同一时间只有十个线程在使用,进行上下文切换的也只有个10个线程吧,线程数量多增加的是内存占用,跟调度开销有什么直接关系呢?

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

问题解决

我在问别人以及查看网上其他人的回答,得出两种解释:

1、第一种是说线程无论是什么状态的,系统都要消耗资源去对其进行维护,个人理解这样与其等待线程越多,上下文切换次数越多的这个理论不搭

2、第二种的话是说其使用的notifyAll(),从而得出等待线程越多,上下文切换次数越多的理论(这种好像有点道理)

经历两天,觉得自己瞎想以及问别人,好像都不能确定到底是什么原因。那为什么不直接找找最权威的人也就是作者大大问问呢?他是写这本书的人,对这个问题是最有权威性的吧。所以,嘿嘿

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

巴拉拉写了一大堆,把自己疑惑以及举例说了一大堆,写完以后反反复复仔仔细细的看自己有没有写错,毕竟这是我第一次间接的和大佬的交流。最后用我那颤抖的手敲击了那最后一下回车。

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

然后第二天中午就得到作者大大的回信了,心里是真的很开心啊啊啊啊,第一次得到大佬的回复。

回信如下:

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

作者大大跟我解释了什么情况下会做上下文切换?嗯?所以上下文切换是和线程的状态无关!

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

归纳总结

怪不得我老大天天和我讲,碰到一个问题不要急着考虑这个问题去想解决他,而是先去想想为什么会发生这个问题。

所以疑惑解决了! 上下文切换无非就是任务从保存再到加载的过程,这个是由OS来决定的,而与线程的状态无关!

换而言之,也就是在线程池中WAITTING状态的线程他不老实,在调度的时候想积极的表现自己,OS也不管线程它是不是在干活还是什么的,从而导致了线程池中空闲的线程参与到了调度,

但是呢,因为他没有抢到锁资源什么都干不了,所以又被OS踢回去了,这里就产生了一次上下文切换。但是实际针对任务来讲是什么都没有做,白白的浪费了上下文切换的时间了。

如果还不理解的话,我们可以看一下针对线程来讲的执行过程:

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

我们这里还要了解一个概念:单核处理器时候怎么支持多线程执行代码的。

CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。

所以就会出现线程还没有执行完同步代码就被挂起了

就比如线程6在执行,但是时间片消耗完,导致线程6被挂起,但是这里注意线程6没有完全执行完同步代码,这里它是没有释放锁的。

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

当CPU调度到线程1的时候因为其没有资源,CPU会重新调度其他线程,调度线程1到其调度其他线程这个过程就浪费了一次上下文调度次数。

在这里我们要注意一点:对于一个已经竞争到同步锁的线程,在其正常运行且还没有走出同步代码块的时候,即使时间片结束也不会释放锁。

这里归纳一下什么时候会释放锁:

  • 当前线程的同步方法、代码块执行结束的时候释放

  • 当前线程在同步方法、同步代码块中遇到break 、 return 终结该代码块或者方法的时候释放。

  • 当前线程出现未处理的error或者exception导致异常结束的时候释放

  • 调用obj.wait()会立即释放锁,当前线程暂停,释放锁,以便其他线程可以执行obj.notify(),但是notify()不会立刻立刻释放sycronized(obj)中的obj锁,必须要等notify()所在线程执行完synchronized(obj)块中的所有代码才会释放这把锁。而 yield(),sleep()不会释放锁。

所以在使用的线程数一定的情况下,空闲的线程越多上下文切换的次数有很大的可能会增加。至于为什么是可能,万一总是调度到抢到锁的线程呢。毕竟这世上不碰巧的事情这么多

答案简单通俗一点来讲就是:

wait的线程太多,一个个都跟我一个已经抢到锁的线程在那抢时间片,你说你抢到cpu执行权有啥用了,你没拿到锁啊气不气啊,锁在我这里啊,你气不气?反正我是很生气,什么都不干还浪费我的时间!拿不到锁你就赶紧走吧切换吧。小崽子!

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

个人吐槽

在公司听过无数大佬的分享,收获了许多。其中有一句话让我影响深刻,他讲:“如果你来到深圳还不努力的话,你来深圳干嘛?来做卧底吗?”。

这个我第一篇自己输出的文章,如果有什么排版有什么问题的地方,请见谅并欢迎指出。对于文章的内容有不同的意见或者错误的地方,欢迎留言指出或者直接加我微信号。欢迎各位大佬一起交流技术带我成长。

告诉我,在《JAVA并发编程的艺术》中这个地方不止我一个人这么想!

原文  https://juejin.im/post/5e11b62d6fb9a047ea7472e1
正文到此结束
Loading...