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. php-curl小记

    用jQuery: $.ajax({ url:url, type:"POST", data:data, contentType:"application/json; cha ...

  2. python中的__all__和__slots__

    python两个有趣属性__all__可用于模块导入时限制,如:from module import *此时被导入模块若定义了__all__属性,则只有all内指定的属性.方法.类可被导入~若没定义, ...

  3. HDU 4726 Kia's Calculation(贪心)

    Kia's Calculation Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others ...

  4. HDU 4706 Children's Day (水题)

    Children's Day Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  5. js比较两个String字符串找出不同,并将不同处高亮显示

    根据java代码改写成js,下边js文件代码: function StringBuffer() { this.__strings__ = []; }; StringBuffer.prototype.a ...

  6. PhotoShop CS6 在2K屏幕下标题菜单等字体太小

    对于此类问题,我更喜欢直接了当,不作解释,解决方法如下(大面积参考互联网内容): (1)Win+R按键打开运行对话框, 输入regedit,打开注册表. (2)展开HKEY_LOCAL_MACHINE ...

  7. sql语句分组/排序/计算总数/连接等sql语句书写

    1.什么是表连接? 答:比如两张表,要获取的信息来自两张表,就需要通过外键的形式进行两张表的连接.最后产后组合信息. 表连接是通过join连接的.表连接说白了就是产生一个大表.表连接也都是用于查询上的 ...

  8. redis分布式锁redisson

    原文:https://blog.csdn.net/Kincym/article/details/78697472 关于redisson的源代码请参考官网:https://github.com/redi ...

  9. 利用UIWebView获取userAgent需要注意的地方

    网络通信有时候需要传递参数userAgent,iOS中获取userAgent很简单. UIWebView* webView = [[UIWebView alloc] initWithFrame:CGR ...

  10. Python3.6学习笔记(二)

    Python 的高级特性 切片 对于指定索引范围取值的操作,Python提供了slice方法,类似于Excel中数据透视表的切片器. >>> L = ['Michael', 'Sar ...