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
= (fair)? 
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,
TimeUnit unit) 
throws InterruptedException
{ };  
//尝试获取一个许可,若在指定的时间内获取成功,则马上返回true。否则则马上返回false
public boolean tryAcquire(int permits)
{ }; 
//尝试获取permits个许可。若获取成功,则马上返回true,若获取失败,则马上返回false
public boolean tryAcquire(int permits, long timeout,
TimeUnit unit) 
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[]
args) {
        int N
8;            //工人数
        Semaphore
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
semaphore;
        public Worker(int num,Semaphore
semaphore){
            this.num
= num;
            this.semaphore
= 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) {
                e.printStackTrace();
            }
        }
    }
}

1)CountDownLatch和CyclicBarrier都可以实现线程之间的等待,仅仅只是它们側重点不同:

    CountDownLatch一般用于某个线程A等待若干个其它线程运行完任务之后,它才运行;

    而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同一时候运行;

    另外,CountDownLatch是不可以重用的。而CyclicBarrier是可以重用的。

2)Semaphore事实上和锁有点类似,它一般用于控制对某组资源的訪问权限。



与AQS有关的并发类的更多相关文章

  1. AQS详解,并发编程的半壁江山

    千呼万唤始出来,终于写到AQS这个一章了,其实为了写这一章,前面也是做了很多的铺垫,比如之前的 深度理解volatile关键字 线程之间的协作(等待通知模式) JUC 常用4大并发工具类 CAS 原子 ...

  2. JUC 并发类概览

    JUC 并发类及并发相关类概览,持续补充... AQS 内部有两个队列,一个等待队列(前后节点),一个条件队列(后继节点),其实是通过链表方式实现: 等待队列是双向链表:条件队列是单向链表:条件队列如 ...

  3. 再谈AbstractQueuedSynchronizer:基于AbstractQueuedSynchronizer的并发类实现

    公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ...

  4. 再谈AbstractQueuedSynchronizer3:基于AbstractQueuedSynchronizer的并发类实现

    公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ...

  5. Java 并发编程-再谈 AbstractQueuedSynchronizer 3 :基于 AbstractQueuedSynchronizer 的并发类实现

    公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ...

  6. 并发编程(二)------并发类容器ConcurrentMap

    并发类容器: jdk5.0以后提供了多种并发类容器来替代同步类容器从而改善性能. 同步类容器的状态都是串行化的. 他们虽然实现了线程安全,但是严重降低了并发性,在多线程环境时,严重降低了应用程序的吞吐 ...

  7. 同步类容器和并发类容器——ConcurrentMap、CopyOnWrite、Queue

     一 同步类容器同步类容器都是线程安全的,但在某些场景中可能需要加锁来保证复合操作. 符合操作如:迭代(反复访问元素,遍历完容器中所有元素).跳转(根据指定的顺序找到当前元素的下一个元素).条件运算. ...

  8. JUC源码分析-集合篇:并发类容器介绍

    JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...

  9. 深入理解Java并发类——AQS

    目录 什么是AQS 为什么需要AQS AQS的核心思想 AQS的内部数据和方法 如何利用AQS实现同步结构 ReentrantLock对AQS的利用 尝试获取锁 获取锁失败,排队竞争 参考 什么是AQ ...

随机推荐

  1. Linux的本地时间和网络时间同步

    Linux本地时间和网络时间不同步,更新了之后,重启还会变回去.可以通过一下方法修改并保存. 1.  安装ntpdate工具 sudo apt-get install ntpdate 2.  设置系统 ...

  2. 如何设置VMware中Linux命令行环境全屏

    在VMware安装Linux后默认屏幕为640×480,如需修改,则请参考以下步骤.以下以CentOS 6.6安装于VMware Workstation 9中为例说明. 1.默认640x480x16, ...

  3. 【原】不定义Order属性,通过切面类的定义顺序来决定通知执行的先后顺序

    [结论] 在多个切面类的“切入点相同”并且每个切面都“没有定义order属性”的情况下,则切面类(中的通知)的执行顺序与该切面类在<aop:config>元素中“声明的顺序”相关,即先声明 ...

  4. C#中结构(struct)的部分初始化和完全初始化

    假设有这样一个值类型struct. public struct Size { public int Length; public int Width; public int Area() { retu ...

  5. xcode4.3.2 arc模式下导入非arc的文件 转

    在arc模式下,我们经常会用到非arc的类库,此时我们可以在Compile Sources下对该文件进行编辑加入 -fno-objc-arc   如图中所示,就可以使用非arc的类库了   转:htt ...

  6. ubuntu下C++和C编程

      一.anjuta Anjuta DevStudio 的官方地址:http://anjuta.sourceforge.net/Anjuta是一个C/C++ IDE,它最大的特色是灵活,同时打开多个文 ...

  7. 很酷的C语言技巧

    C语言常常让人觉得它所能表达的东西非常有限.它不具有类似第一级函数和模式匹配这样的高级功能.但是C非常简单,并且仍然有一些非常有用的语法技巧和功能,只是没有多少人知道罢了. 指定的初始化 很多人都知道 ...

  8. 深入分析JavaWeb Item7 -- HttpServletResponse详解

    Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象.和代表响应的response对象.request和response对象即然代表请求和响应,那我们要 ...

  9. Java IO 体系结构

    参考文章地址: http://blog.csdn.net/oracle_microsoft/article/details/2634231 Java IO体系结构看似庞大复杂,其实有规律可循,要弄清楚 ...

  10. 第一章 Java工具类目录

    在这一系列博客中,主要是记录在实际开发中会常用的一些Java工具类,方便后续开发中使用. 以下的目录会随着后边具体工具类的添加而改变. 浮点数精确计算 第二章 Java浮点数精确计算 crc32将任意 ...