面试官:你给我说一下线程池里面的几个锁吧

你好呀,我是歪歪 。
最近有个读者给我说,面试聊到线程池的时候,相谈甚欢,基本都回答上来了,但是其中有一个问题直接把他干懵逼了 。
面试官问他:你说一下线程池里面的锁吧 。
结果他关于线程池的知识点其实都是在各个博客或者面经里面看到的,没有自己去翻阅过源码,也就根本就没有注意过线程池里面还有锁的存在 。
他还给我抱怨:
他这么一说,我也觉得,好像大家聊到线程池的时候,都没有怎么聊到里面用到的锁 。
确实是存在感非常低 。
要不我就安排一下?
mainLock其实线程池里面用到锁的地方还是非常的多的 。
比如我之前说过,线程池里面有个叫做 workers 的变量,它存放的东西,可以理解为线程池里面的线程 。
而这个对象的数据结构是 HashSet 。
HashSet 不是一个线程安全的集合类,这你知道吧?
所以,你去看它上面的注释是怎么说的:
当持有 mainLock 这个玩意的时候,才能被访问 。
就算我不介绍,你看名字也能感觉的到:如果没有猜测的话,那么 mainLock 应该是一把锁 。
到底是不是呢,如果是的话,它又是个什么样子的锁呢?
在源码中 mainLock 这个变量,就在 workers 的正上方:
原来它的真身就是一个 ReentrantLock 。
用一个 ReentrantLock 来保护一个 HashSet,完全没毛病 。
那么 ReentrantLock 和 workers 到底是怎么打配合的呢?
我们还是拿最关键的 addWorker 方法来说:
用到锁了,那么必然是有什么东西需要被被独占起来的 。
你再看看,你加锁独占了某个共享资源,你是想干什么?
绝大部分情况下,肯定是想要改变它,往里面塞东西,对不对?
所以你就按照这个思路分析,addWorker 中被锁包裹起来的这段代码,它到底在独占什么东西?
其实都不用分析了,这里面的共享数据一共就两个 。两个都需要进行写入操作,这两共享数据,一个是workers 对象,一个是 largestPoolSize 变量 。
workers 我们前面说了,它的数据结构是线程不安全的 HashSet 。
largestPoolSize 是个啥玩意,它为什么要被锁起来?
这个字段是用来记录线程池中,曾经出现过的最大线程数 。
包括读取这个值的时候也是加了 mianLock 锁的:
其实我个人觉得这个地方用 volatile 修饰一下 largestPoolSize 变量,就可以省去 mainLock 的上锁操作 。
同样也是线程安全的 。
不知道你是不是也是这样觉得的?
如果你也是这样想的话,不好意思,你想错了 。
在线程池里面其他的很多字段都用到了 volatile:
为什么 largestPoolSize 不用呢?
你再看一下前面 getLargestPoolSize 方法获取值的地方 。
如果修改为 volatile,不上锁,就少了一个 mainLock.lock() 的操作 。
去掉这个操作,就有可能少了一个阻塞等待的操作 。
假设 addWorkers 方法还没来得及修改 largestPoolSize 的值,就有线程调用了 getLargestPoolSize 方法 。
由于没阻塞,直接获取到的值,只是那一瞬间的 largestPoolSize,不是一定是 addWorker 方法执行完成后的
加上阻塞,程序是能感知到 largestPoolSize 有可能正在发生变化,所以获取到的一定是 addWorker 方法执行完成后的 largestPoolSize 。
所以我理解加锁,是为了最大程度上保证这个参数的准确性 。
除了前面说的几个地方外,还是有很多 mainLock 使用的地方:
我就不一一介绍了,你得自己去翻一翻,这玩意介绍起来也没啥意思,都是一眼就能瞟明白的代码 。
说个有意思的 。
你有没有想过这里 Doug Lea 老爷子为什么用了线程不安全的 HashSet,配合 ReentrantLock 来实现线程安全呢?

推荐阅读