文档章节

AbstractQueuedSynchronizer在工具类Semaphore、CountDownLatch、ReentrantLock中的应用和CyclicBarrier

pczhangtl
 pczhangtl
发布于 2013/11/18 11:27
字数 1334
阅读 79
收藏 1

在上篇文章本人粗略地整理了AbstractQueuedSynchronizer和ReentrantLock的源码要点。其实,在java.util.concurrent包中,AbstractQueuedSynchronizer的应用非常广泛,而不局限于在ReentrantLock中的实现,本文简要介绍下AbstractQueuedSynchronizer在Semaphore、ReentrantReadWriteLock等类中的应用

0. 回顾

上文在介绍AQS的时候,介绍了AQS和ReentrantLock类中的Sync子类互相配合完成可重入锁的实现,在这其中AQS所提供的是一套灵活和完整的队列处理机制。由于在AQS中已经提供了完整的队列处理机制,通常是不需要扩展的子类Override的。同时,AQS又提供了state属性和tryAcquire()/tryRelease()等方法,而这些正是需要子类根据具体的需求逻辑灵活实现的扩展点。从ReentrantLock、ReentrantReadWriteLock、Semaphore和CountDownLatch的实现来看,通常是在这些工具类的中封装实现自己独有的Sync内部类,而Sync就是对AQS的扩展实现。

1. Semaphore

学习操作系统理论课的时候,教材上应该都会讲过信号量这种概念,java.util.concurrent.Semaphore类就是Java中这个概念的实现。比如资源R有5个实体,如果每个线程执行的过程中需要用到1个,那么允许5个线程并发执行,第6个会等待其他线程释放资源后继续执行。

先看一个应用Semaphore的例子:
其中最主要的方法就是acquire()和release(),更详细的可以参看Oracle的API文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ExecutorService exec = Executors.newCachedThreadPool();
        finalSemaphore sem =newSemaphore(5);
 
        for(inti =1; i <100; i++) {
            finalinttid = i;
            Runnable semTask =newRunnable() {
                publicvoidrun() {
                    try{
                        sem.acquire();
 
                        System.out.println("running thread with id: "+ tid);
                        Thread.sleep((long) (Math.random() *3000));
                        System.out.println("completing with id: "+ tid);
 
                        sem.release();
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            exec.execute(semTask);
        }
        exec.shutdown();

下面看看Semaphore的实现。如果熟悉ReentrantLock的实现,那么Semaphore其实很好理解,简单来讲,Semaphore实际上就是把锁的限制从1变为N。

  • state存储的是表示剩余可用资源的值
  • Node采用的是SHARED模式
  • 获得锁之后会尝试传播,释放更多锁

2. CountDownLatch

这个类主要是为了解决多线程中的状态依赖问题。java.util.concurrent.CountDownLatch这个类从名字就可以看出,是以一个递减计数器为基础,多个线程共享这样一个对象,开启一个计数值,某些线程可以等待这个计数器值为0的时候继续任务,调用await(),而那些改变状态的线程需要做的就是使计数器递减,调用countDown()方法。

看一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ExecutorService exec = Executors.newCachedThreadPool();
        finalCountDownLatch cdl =newCountDownLatch(3);
 
        Runnable watingTasks =newRunnable() {
            publicvoidrun() {
                try{
                    System.out.println("there're 3 tasks here. if all tasks are finished, i will go home.");
                    System.out.println("working...");
 
                    cdl.await();
 
                    System.out.println("ok, i will go home now!");
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        exec.execute(watingTasks);
 
        for(inti =0; i <3; i++) {
            finalinttid = i;
            Runnable semTask =newRunnable() {
                publicvoidrun() {
                    try{
                        System.out.println("starting task "+ tid + "...");
                        Thread.sleep((long) (Math.random() *5000));
                        System.out.println("task "+ tid + " finished");
 
                        cdl.countDown();
 
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            exec.execute(semTask);
        }
        exec.shutdown();

和Semaphore类似,在使用AQS的实现上,主要有以下几点。

  • state存储的是count计数变量
  • Node采用的是SHARED模式
  • countDown()调用tryReleaseShared使得计数减1
  • await是调用tryAcquire,实际上就是判断state是否为0

3. ReentrantReadWriteLock

在高并发场景下,为了让任务执行更有效率,将读和写场景分离是有必要的。这是因为读和写在线程安全方面特点的不同,读不改变状态,多个线程是可以同时进行而没有问题的,而写与写、写与读之间都是需要互斥的。在java.util.concurrent中,ReentrantReadWriteLock这个类就是做这个事情的。

具体的使用就不举例了,直接分析下其实现。从类名上看ReentrantLock和ReentrantReadWriteLock就很相似,实现上也有相似的部分。ReentrantReadWriteLock中封装了ReadLock和WriteLock内部类,而ReentrantReadWriteLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock中都有自己的Sync类属性,使用的是ReentrantReadWriteLock.Sync实现,而且对象关系上,ReadLock和WriteLock中的sync都是指向ReentrantReadWriteLock对象中的sync引用,即使用了同一个AQS同一套队列,只是将方法分离开来处理。

其中的要点:

  • state同时存储r和w的个数
  • WriteLock的锁操作类似ReentrantLock使用互斥节点
  • 而readlock使用共享节点
  • 读锁写锁的逻辑在各自的tryLock中,最终实现在ReadWriteLock的tryReadLock和tryWriteLock中

4. 最后再说下CyclicBarrier类

在这里说java.util.concurrent.CyclicBarrier,并非因为其使用了AQS,而是因为它的用法和CountDownLatch有类似之处。CyclicBarrier和CountDownLatch都是处理状态依赖的问题的,而不同之处是使用CyclicBarrier的线程互相依赖,即互相等待,直到达到某一特定状态,这些线程同时继续执行。

CyclicBarrier是基于ReetrantLock和ConditionObject的,await()的时候对计数器递减,并检查是否为0,如果为0则执行CyclicBarrier类对象的barrierCommand(Runnable类对象属性)并signalAll()通知所有等待线程开始下一轮,否则阻塞当前线程。

本文对java.util.concurrent的并发工具类和AQS的应用做了简要的整理,更多并发的内容会在后面的文章整理。

本文转载自:http://www.molotang.com/articles/487.html

pczhangtl
粉丝 46
博文 707
码字总数 113318
作品 0
浦东
高级程序员
私信 提问
显式锁(java.util.Concurrent)

一、前言   在分析完了集合框架后,很有必要接着分析java并发包下面的源码,JUC(java.util.concurrent)源码也是我们学习Java迈进一步的重要过程。我们分为几个模块进行分析,首先是对锁模...

狼王黄师傅
2018/11/27
45
0
concurrent包的同步器

concurrent包的同步器:CountDownLatch、CyclicBarrier、Semaphore 同步器简介 名称 功能 构成 主要方法 CountDownLatch(闭锁) 一个线程等待其它线程完成各自工作后在执行 继承aqs await()...

GITTODO
2018/04/25
7
0
Java 并发之 AbstractQueuedSynchronizer

如果你读过 JUC 中 ReentrantLock、CountDownLatch、FutureTask、Semaphore 等的源代码,会发现其中都有一个名为 Sync 的类,而这个类是以 AbstractQueuedSynchronizer 为基础的,所以说 Ab...

编走编想
2013/10/30
265
0
Semaphore CountDownLatch CyclicBarrier 源码分析

java5 中 ,提供了几个并发工具类 ,Semaphore CountDownLatch CyclicBarrier,在并发编程中非常实用。前两者通过 内部类sync 继承AQS,使用共享资源的模式,AQS的实现可参考我的另一篇 AQS ...

ovirtKg
2016/10/19
59
0
JUC整体架构图

JUC相关整体框架图 整体架构.png JUC相关UML图 reentrantlock uml图 reentrantreadwritelock uml图 countdownlatch uml图 semaphore uml图 cyclicbarrier uml图...

小鱼嘻嘻
2018/01/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

uni app 零基础小白到项目实战

$emit 子组件传给父组件$ref 父组件操作子组件 公用模板 uni-app全局变量的几种实现方法 const websiteUrl = 'http'const now = Date.now || function() { return new Date().getTime......

达达前端小酒馆
32分钟前
7
0
Tomcat是如何实现异步Servlet的

前言 通过我之前的Tomcat系列文章,相信看我博客的同学对Tomcat应该有一个比较清晰的了解了,在前几篇博客我们讨论了Tomcat在SpringBoot框架中是如何启动的,讨论了Tomcat的内部组件是如何设...

木木匠
57分钟前
31
0
mysql中间件分享(Mysql-prxoy,Atlas,DBProxy,Amoeba,cobar,TDDL)

hello 各位小伙伴大家好,我是小栈君,这期我们分享关于mysql中间件的研究,也就是数据层的读写分离和负载均衡,希望能够在实际的应用中能够帮助到各位小伙伴。 下期我们将继续分享go语言的系...

IT干货栈
今天
15
0
OSChina 周一乱弹 —— 人生,还真是到处是意外

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @这次装个文艺青年吧 :#今日歌曲推荐# 分享lil peep的单曲《High School》 《High School》- lil peep 手机党少年们想听歌,请使劲儿戳(这里...

小小编辑
今天
1K
13
Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。 一、ThreadPoolTaskExecutor 本文采用 Executors 的工厂...

CREATE_17
今天
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部