与AQS有关的并发类
ReetrantLock与Condition:
在java.util.concurrent包中。有两个非常特殊的工具类。Condition和ReentrantLock,使用过的人都知道,ReentrantLock(重入锁)是jdk的concurrent包提供的一种独占锁的实现。它继承自Dong Lea的 AbstractQueuedSynchronizer(同步器),确切的说是ReentrantLock的一个内部类继承了AbstractQueuedSynchronizer。ReentrantLock仅仅只是是代理了该类的一些方法,可能有人会问为什么要使用内部类在包装一层?
我想是安全的关系,由于AbstractQueuedSynchronizer中有非常多方法,还实现了共享锁,Condition(稍候再细说)等功能,假设直接使ReentrantLock继承它,则非常easy出现AbstractQueuedSynchronizer中的API被误用的情况。
ReentrantLock和Condition的使用方式一般是这种:
执行后,结果例如以下:
能够看到,
Condition的运行方式,是当在线程1中调用await方法后。线程1将释放锁,而且将自己沉睡。等待唤醒,
线程2获取到锁后。開始做事,完成后,调用Condition的signal方法,唤醒线程1,线程1恢复运行。
以上说明Condition是一个多线程间协调通信的工具类。使得某个,或者某些线程一起等待某个条件(Condition),仅仅有当该条件具备( signal 或者 signalAll方法被带调用)时 ,这些等待线程才会被唤醒,从而又一次争夺锁。
那。它是怎么实现的呢?
首先还是要明确。reentrantLock.newCondition() 返回的是Condition的一个实现,该类在AbstractQueuedSynchronizer中被实现。叫做newCondition()
关键的就在于此,我们知道AQS自己维护的队列是当前等待资源的队列。AQS会在资源被释放后,依次唤醒队列中从前到后的全部节点,使他们相应的线程恢复运行。直到队列为空。
而Condition自己也维护了一个队列,该队列的作用是维护一个等待signal信号的队列,两个队列的作用是不同,其实。每一个线程也只会同一时候存在以上两个队列中的一个,流程是这种:
1. 线程1调用reentrantLock.lock时,线程被增加到AQS的等待队列中。
2. 线程1调用await方法被调用时。该线程从AQS中移除,相应操作是锁的释放。
3. 接着立即被增加到Condition的等待队列中,以为着该线程须要signal信号。
4. 线程2,由于线程1释放锁的关系。被唤醒。并推断能够获取锁。于是线程2获取锁。并被增加到AQS的等待队列中。
5. 线程2调用signal方法,这个时候Condition的等待队列中仅仅有线程1一个节点,于是它被取出来,并被增加到AQS的等待队列中。 注意,这个时候,线程1 并没有被唤醒。
6. signal方法运行完成,线程2调用reentrantLock.unLock()方法,释放锁。这个时候由于AQS中仅仅有线程1,于是。AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒。于是线程1回复运行。
7. 直到释放所整个过程运行完成。
能够看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的。Condition作为一个条件类,非常好的自己维护了一个等待信号的队列,并在适时的时候将结点增加到AQS的等待队列中来实现的唤醒操作。
CyclicBarrier: 循环的篱笆。
它同意一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中。这些线程必须不时地互相等待。此时
CyclicBarrier 非常实用。
当某一个线程到达公共屏障点后(即完毕一部分任务后),调用awaite(),等待其它线程到来。一起走。
能够看成是一个集结线程类。构造函数传入等待线程的数量
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class CyclicBarrierTest {
public static void main(String [] args){
ExecutorService service=Executors.newCachedThreadPool();
final CyclicBarrier cb=new CyclicBarrier(3); //三个线程同一时候到达
for(int i=0;i<3;i++){
Runnable runnable=new Runnable(){
public void run(){
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程"+Thread.currentThread().getName()+
"即将到达集合地点1,当前已有"+(cb.getNumberWaiting()+1)+"个已到达"+
(cb.getNumberWaiting()==2? "都到齐了。继续走啊":"正在等候"));
try {
cb.await();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程"+Thread.currentThread().getName()+
"即将到达集合地点2。当前已有"+(cb.getNumberWaiting()+1)+"个已到达"+
(cb.getNumberWaiting()==2? "都到齐了。继续走啊":"正在等候"));
try {
cb.await();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程"+Thread.currentThread().getName()+
"即将到达集合地点3,当前已有"+(cb.getNumberWaiting()+1)+"个已到达"+
(cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
try {
cb.await();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
}
结果:
线程pool-1-thread-3即将到达集合地点1,当前已有1个已到达正在等候
线程pool-1-thread-2即将到达集合地点1,当前已有2个已到达正在等候
线程pool-1-thread-1即将到达集合地点1,当前已有3个已到达都到齐了。继续走啊
线程pool-1-thread-1即将到达集合地点2,当前已有1个已到达正在等候
线程pool-1-thread-2即将到达集合地点2,当前已有2个已到达正在等候
线程pool-1-thread-3即将到达集合地点2。当前已有3个已到达都到齐了。继续走啊
线程pool-1-thread-2即将到达集合地点3,当前已有1个已到达正在等候
线程pool-1-thread-1即将到达集合地点3,当前已有2个已到达正在等候
线程pool-1-thread-3即将到达集合地点3。当前已有3个已到达都到齐了,继续走啊
CountDownLatch: 它同意一个或多个线程一直等待,直到其它线程的操作运行完后再运行。
比如。应用程序的主线程希望在负责启动框架服务的线程已经启动全部的框架服务之后再运行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。
每当一个线程完毕了自己的任务后,计数器的值就会减1。当计数器值到达0时。它表示全部的线程已经完毕了任务,然后在闭锁上等待的线程就能够恢复运行任务。
过程:1,主线程开启
2,new出等待N个线程的CountDownLatch(构造函数传入须要等待的线程数量)
3,create并运行N个线程
4。主线程等待
5。N个线程都运行完成
6,主线程恢复运行。
与CountDownLatch的第一次交互是主线程等待其它线程。
主线程必须在启动其它线程后马上调用CountDownLatch对象的await()方法。
这样主线程的操作就会在这种方法上堵塞,直到其它线程完毕各自的任务。
其它N 个线程必须引用闭锁对象,由于他们须要通知CountDownLatch对象。他们已经完毕了各自的任务。
这样的通知机制是通过 CountDownLatch.countDown()方法来完毕的;每调用一次这种方法。在构造函数中初始化的count值就减1。
所以当N个线程都调
用了这种方法,count的值等于0,然后主线程就能通过await()方法,恢复运行自己的任务(一旦其它线程已经開始运行,就能够调用CountDownLatch对象的awaite方法,等待其它线程运行完成后,开启主线程)。
Semaphore翻译成字面意思为 信号量,Semaphore能够控同一时候訪问的线程个数,通过 acquire() 获取一个许可。假设没有就等待,而 release() 释放一个许可。
Semphore:信号量 參考,
Semaphore类位于java.util.concurrent包下,它提供了2个构造器:
1
2
3
4
5
6
|
public Semaphore( int permits) //參数permits表示许可数目,即同一时候能够同意多少线程进行訪问 sync new NonfairSync(permits); } public Semaphore( int permits, boolean fair) //这个多了一个參数fair表示是否是公平的,即等待时间越久的越先获取许可 sync new FairSync(permits) new NonfairSync(permits); } |
以下说一下Semaphore类中比較重要的几个方法。首先是acquire()、release()方法:
1
2
3
4
|
public void acquire() throws InterruptedException //获取一个许可 public void acquire( int permits) throws InterruptedException //获取permits个许可 public void release() //释放一个许可 public void release( int permits) //释放permits个许可 |
acquire()用来获取一个许可,若无许可可以获得。则会一直等待,直到获得许可。
release()用来释放许可。注意。在释放许可之前。必须先获获得许可。
这4个方法都会被堵塞,假设想马上得到运行结果。能够使用以下几个方法:
1
2
3
4
|
public boolean tryAcquire() //尝试获取一个许可。若获取成功。则马上返回true。若获取失败。则马上返回false public boolean tryAcquire( long timeout, throws InterruptedException //尝试获取一个许可,若在指定的时间内获取成功,则马上返回true。否则则马上返回false public boolean tryAcquire( int permits) //尝试获取permits个许可。若获取成功,则马上返回true,若获取失败,则马上返回false public boolean tryAcquire( int permits, long timeout, throws InterruptedException //尝试获取permits个许可,若在指定的时间内获取成功,则马上返回true,否则则马上返回false |
另外还能够通过availablePermits()方法得到可用的许可数目。
以下通过一个样例来看一下Semaphore的详细使用:
假若一个工厂有5台机器。可是有8个工人。一台机器同一时候仅仅能被一个工人使用,仅仅有使用完了,其它工人才干继续使用。(有人将这个类的应用归结为厕所理论。事实上都是一样的)那么我们就能够通过Semaphore来实现:
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
|
public class Test public static void main(String[] int N 8 ; //工人数 Semaphore new Semaphore( 5 ); //机器数目 for ( int i= 0 ;i<N;i++) new Worker(i,semaphore).start(); } static class Worker extends Thread{ private int num; private Semaphore public Worker( int num,Semaphore this .num this .semaphore } @Override public void run() try { semaphore.acquire(); System.out.println( "工人" + this .num+ "占用一个机器在生产..." ); Thread.sleep( 2000 ); System.out.println( "工人" + this .num+ "释放出机器" ); semaphore.release(); } catch (InterruptedException e.printStackTrace(); } } } } |
1)CountDownLatch和CyclicBarrier都可以实现线程之间的等待,仅仅只是它们側重点不同:
CountDownLatch一般用于某个线程A等待若干个其它线程运行完任务之后,它才运行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同一时候运行;
另外,CountDownLatch是不可以重用的。而CyclicBarrier是可以重用的。
2)Semaphore事实上和锁有点类似,它一般用于控制对某组资源的訪问权限。
与AQS有关的并发类的更多相关文章
- AQS详解,并发编程的半壁江山
千呼万唤始出来,终于写到AQS这个一章了,其实为了写这一章,前面也是做了很多的铺垫,比如之前的 深度理解volatile关键字 线程之间的协作(等待通知模式) JUC 常用4大并发工具类 CAS 原子 ...
- JUC 并发类概览
JUC 并发类及并发相关类概览,持续补充... AQS 内部有两个队列,一个等待队列(前后节点),一个条件队列(后继节点),其实是通过链表方式实现: 等待队列是双向链表:条件队列是单向链表:条件队列如 ...
- 再谈AbstractQueuedSynchronizer:基于AbstractQueuedSynchronizer的并发类实现
公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ...
- 再谈AbstractQueuedSynchronizer3:基于AbstractQueuedSynchronizer的并发类实现
公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ...
- Java 并发编程-再谈 AbstractQueuedSynchronizer 3 :基于 AbstractQueuedSynchronizer 的并发类实现
公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ...
- 并发编程(二)------并发类容器ConcurrentMap
并发类容器: jdk5.0以后提供了多种并发类容器来替代同步类容器从而改善性能. 同步类容器的状态都是串行化的. 他们虽然实现了线程安全,但是严重降低了并发性,在多线程环境时,严重降低了应用程序的吞吐 ...
- 同步类容器和并发类容器——ConcurrentMap、CopyOnWrite、Queue
一 同步类容器同步类容器都是线程安全的,但在某些场景中可能需要加锁来保证复合操作. 符合操作如:迭代(反复访问元素,遍历完容器中所有元素).跳转(根据指定的顺序找到当前元素的下一个元素).条件运算. ...
- JUC源码分析-集合篇:并发类容器介绍
JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...
- 深入理解Java并发类——AQS
目录 什么是AQS 为什么需要AQS AQS的核心思想 AQS的内部数据和方法 如何利用AQS实现同步结构 ReentrantLock对AQS的利用 尝试获取锁 获取锁失败,排队竞争 参考 什么是AQ ...
随机推荐
- Xcode 中的IOS工程模板
1.IOS模板主要分为: Application .Framework.Other application 分为:Master-Detail Application 可以构建树形导航模式引用,生成的代 ...
- 在IDEA(phpStorm)中使用Babel编译ES6
安装Babel 官方文档建议我们根据单个项目进行本地安装,原因是不同的项目可以依赖不同版本的 Babel,使你的项目更方便移植.更易于安装. 在项目的根目录下使用命令行工具(CMD等)执行下面代码 n ...
- Centos安装Perforce
Author: JinDate: 20140827System: CentOS release 6.5 (Final) 参考:http://www.cnblogs.com/itech/archive/ ...
- C#遍历系统所安装的打印机,使用WMI方式获取打印机的所有属性
有网友发消息来询问,C#如何遍历系统已经安装的所有打印机,并获得每个打印机的相关信息,如:端口,名称等等 C#里面,虽然在 System.Drawing.Printing 这个namespace下,提 ...
- msgpack和protobuf的对比
msgpack和protobuf的对比 msgpack的序列化速度比protobuf要快一些,但反序列化要比protobuf要慢一些,但总体都接近msgpack可以直接序列化类对象,但protobuf ...
- IOS开发之深拷贝与浅拷贝(mutableCopy与Copy)详解
copy与retain的区别: copy是创建一个新对象,retain是创建一个指针,引用对象计数加1.Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象 ...
- 【c语言】模拟实现库函数的atof函数
// 模拟实现库函数的atof函数 #include <stdio.h> #include <string.h> #include <assert.h> #incl ...
- 算法:哈希表格(Hash Table)
背景 Java 和 .Net 平台都有一个所有引用类型都会间接或直接继承的类型:Object,这个类型提供最基本的相等性比较算法和哈希算法,很多书上都给出了在重写这两个算法的时候的主意事项,其中大多数 ...
- 《软件开发与创新:ThoughtWorks文集:续集》
<软件开发与创新:ThoughtWorks文集:续集> 基本信息 原书名:The thoughtWorks anthology, volume 2:More essays on softw ...
- python限制进程、子进程占用内存大小、CPU时间的方法:resource模块
内置模块:resource 在mac环境下功能会存在问题.linux下可以使用:但是for i in range(10000)的值必须是10000或者更大的数值才有用.没有搞清楚为什么 #/usr/b ...