分享

阻塞队列来看并发库中对线程阻塞的实现

 IT乐知 2020-07-15

上一篇已经实现了一个阻塞队列,阻塞队列的关键是则是线程,那么并发库中的类是如何阻塞线程的呢?

阻塞队列的再次优化

在上一篇文章《多线程并发基础通过Object的方法实现阻塞队列》中通过层层优化,最终采用Object的wait()与notifyAll()就实现了一个高响应并且不浪费CPU的阻塞队列。

在Java的java.util.concurrent也提供了一些类能够实现这个功能,在之前学习的Lock接口时讲了他提供的几个主要方法作为获取锁和释放锁的基本功能,但是最后一个方法“Condition newCondition()”并没有详细去了解,而它也是实现阻塞队列的关键,直接看代码:

I)YJ4R_U{$KHBR_}7YY6VBY

通过两个Condition对象notFull、notEmpty分别控制控制put与take方法的阻塞,同时put或take方法执行成功又会通知notEmpty或notFull可以继续执行,这种方式的实现比wait()与notifyAll()方式更加复杂,但是他们的精细度更高,notifyAll()会唤醒所有等待中的线程去竞争同一个锁,put方法成功还是有可能让另外一个put方法获取到锁,而Condition方式则只会唤醒需要唤醒的线程,减少了锁竞争。

Condition的实现与内置的wait()与notifyAll()对比就好像Lock与synchronized的对比,实现方案可能更加复杂,不过却更加的灵活并且粒度更细,实现更加自主可能提高更好的性能。

通过对缓存队列的实现可以看出除了对数据的同步控制外,还有就是要在合适的地方对阻塞线程和唤醒线程,当队列为空时阻塞take线程,当队列满了的时候阻塞put线程,take成功后队列不在处于饱满状态要唤醒put线程,put成功后队列肯定不在是空队列所以要唤醒take线程。所有的阻塞与唤醒都是基于对队列状态的判断

AbstractQueuedSynchronizer

那么再来看Java中原生的一些并发类,比如Lock的lock方法获取锁也会阻塞,而unlock方法会通知所有lock阻塞线程再次竞争锁。信号量Semaphore的acquire()方法获取许可如果没有获取到就阻塞,release()方法释放获取的许可,其他正在阻塞的就可以获取到然后继续执行,闭锁CountDownLatch的await()可以阻塞线程,countDown()方法执行一次就减少预设的次数,当设置的值被减到0时所有await阻塞的线程开始执行。

与我们实现的阻塞队列一样,都是一个方法在阻塞线程,然后等待另外一个方法来释放信号,这些类说到底还是竞争资源,那么他们竞争的是什么资源呢?通过源码可以得出ReentrantLock、Semaphore、CountDownLatch内部都维护这一个抽象类AbstractQueuedSynchronizer的继承类

AbstractQueuedSynchronizer中维护着一个int型的state,这个state就像我们实现的阻塞队列中的队列状态一样是所谓的资源,不同的操作对应state的不同处理。

几个阻塞线程类的实现

ReentrantLock的lock方法实际上是调用compareAndSetState(int expect, int update)方法尝试把state从0设置为1,unlock则是把1改回0,ReentrantLock中还维护一个exclusiveOwnerThread用来保存拥有锁的线程,在lock的时候设置这个值,unlock时把这个值设为null。

Semaphore的处理方法,在构造方法“public Semaphore(int permits)”中把state的值初始化为permits的值,acquire()方法会通过compareAndSetState(int expect, int update)减少state的值,会优先计算减少后的值,如果小于0会进入等待队列中等待。而release()方法则会增加state值。

CountDownLatch和信号量有点相似,先初始化state一个值,不同的是后面的处理,countDown()会更新state减少这个值直到0,而await则是去判断这个值是否这个值是否等于0,不等于0就阻塞。

总结

通过利用再次对阻塞队列的优化,知道了如何优美的阻塞线程和再次启动,通过判断队列状态并利用Lock来实现阻塞与唤醒,那么Lock又是如何阻塞和唤醒线程的呢?通过对lock方法和unlock方法源码可以看到它维护这一个AbstractQueuedSynchronizer的子类,通过对AbstractQueuedSynchronizer的state状态来判断是否阻塞,通过compareAndSetState方法和AbstractQueuedSynchronizer的阻塞队列来实现线程的阻塞,这两块并没有深入,等深入后再来解惑吧。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多