Java多线程20:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger
前言
在多线程环境下,JDK给开发者提供了许多的组件供用户使用(主要在java.util.concurrent下),使得用户不需要再去关心在具体场景下要如何写出同时兼顾线程安全性与高效率的代码。之前讲过的线程池、BlockingQueue都是在java.util.concurrent下的组件,Timer虽然不在java.util.concurrent下,但也算是。后两篇文章将以例子的形式简单讲解一些多线程下其他组件的使用,不需要多深刻的理解,知道每个组件大致什么作用就行。
本文主要讲解的是CountDownLatch、Semaphore、Exchanger。
CountDownLatch
CountDownLatch主要提供的机制是当多个(具体数量等于初始化CountDownLatch时count参数的值)线程都达到了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己的后续工作。值得注意的是,CountDownLatch是可以唤醒多个等待的线程的。
到达自己预期状态的线程会调用CountDownLatch的countDown方法,等待的线程会调用CountDownLatch的await方法。如果CountDownLatch初始化的count值为1,那么这就退化为一个单一事件了,即是由一个线程来通知其他线程,效果等同于对象的wait和notifyAll,count值大于1是常用的方式,目的是为了让多个线程到达各自的预期状态,变为一个事件进行通知,线程则继续自己的行为。
看一个例子:
private static class WorkThread extends Thread
{
private CountDownLatch cdl;
private int sleepSecond; public WorkThread(String name, CountDownLatch cdl, int sleepSecond)
{
super(name);
this.cdl = cdl;
this.sleepSecond = sleepSecond;
} public void run()
{
try
{
System.out.println(this.getName() + "启动了,时间为" + System.currentTimeMillis());
Thread.sleep(sleepSecond * 1000);
cdl.countDown();
System.out.println(this.getName() + "执行完了,时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
} private static class DoneThread extends Thread
{
private CountDownLatch cdl; public DoneThread(String name, CountDownLatch cdl)
{
super(name);
this.cdl = cdl;
} public void run()
{
try
{
System.out.println(this.getName() + "要等待了, 时间为" + System.currentTimeMillis());
cdl.await();
System.out.println(this.getName() + "等待完了, 时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
} public static void main(String[] args) throws Exception
{
CountDownLatch cdl = new CountDownLatch(3);
DoneThread dt0 = new DoneThread("DoneThread1", cdl);
DoneThread dt1 = new DoneThread("DoneThread2", cdl);
dt0.start();
dt1.start();
WorkThread wt0 = new WorkThread("WorkThread1", cdl, 2);
WorkThread wt1 = new WorkThread("WorkThread2", cdl, 3);
WorkThread wt2 = new WorkThread("WorkThread3", cdl, 4);
wt0.start();
wt1.start();
wt2.start();
}
看一下运行结果:
DoneThread2要等待了, 时间为1444563077434
DoneThread1要等待了, 时间为1444563077434
WorkThread1启动了,时间为1444563077434
WorkThread3启动了,时间为1444563077435
WorkThread2启动了,时间为1444563077435
WorkThread1执行完了,时间为1444563079435
WorkThread2执行完了,时间为1444563080435
WorkThread3执行完了,时间为1444563081435
DoneThread1等待完了, 时间为1444563081435
DoneThread2等待完了, 时间为1444563081435
效果十分明显,解释一下:
1、启动2个线程DoneThread线程等待3个WorkThread全部执行完
2、3个WorkThread全部执行完,最后执行完的WorkThread3执行了秒符合预期
3、后三句从时间上看几乎同时出现,说明CountDownLatch设置为3,WorkThread3执行完,两个wait的线程马上就执行后面的代码了
这相当于是一种进化版本的等待/通知机制,它可以的实现的是多个工作线程完成任务后通知多个等待线程开始工作,之前的都是一个工作线程完成任务通知一个等待线程或者一个工作线程完成任务通知所有等待线程。
CountDownLatch其实是很有用的,特别适合这种将一个问题分割成N个部分的场景,所有子部分完成后,通知别的一个/几个线程开始工作。比如我要统计C、D、E、F盘的文件,可以开4个线程,分别统计C、D、E、F盘的文件,统计完成把文件信息汇总到另一个/几个线程中进行处理
Semaphore
Semaphore是非常有用的一个组件,它相当于是一个并发控制器,是用于管理信号量的。构造的时候传入可供管理的信号量的数值,这个数值就是控制并发数量的,我们需要控制并发的代码,执行前先通过acquire方法获取信号,执行后通过release归还信号 。每次acquire返回成功后,Semaphore可用的信号量就会减少一个,如果没有可用的信号,acquire调用就会阻塞,等待有release调用释放信号后,acquire才会得到信号并返回。
Semaphore分为单值和多值两种:
1、单值的Semaphore管理的信号量只有1个,该信号量只能被1个,只能被一个线程所获得,意味着并发的代码只能被一个线程运行,这就相当于是一个互斥锁了
2、多值的Semaphore管理的信号量多余1个,主要用于控制并发数
看一下代码例子:
public static void main(String[] args)
{
final Semaphore semaphore = new Semaphore(5); Runnable runnable = new Runnable()
{
public void run()
{
try
{
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "获得了信号量,时间为" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "释放了信号量,时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
semaphore.release();
}
}
}; Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++)
threads[i] = new Thread(runnable);
for (int i = 0; i < threads.length; i++)
threads[i].start();
}
看一下运行结果:
Thread-1获得了信号量,时间为1444557040464
Thread-2获得了信号量,时间为1444557040465
Thread-0获得了信号量,时间为1444557040464
Thread-3获得了信号量,时间为1444557040465
Thread-4获得了信号量,时间为1444557040465
Thread-2释放了信号量,时间为1444557042466
Thread-4释放了信号量,时间为1444557042466
Thread-0释放了信号量,时间为1444557042466
Thread-1释放了信号量,时间为1444557042466
Thread-3释放了信号量,时间为1444557042466
Thread-9获得了信号量,时间为1444557042467
Thread-7获得了信号量,时间为1444557042466
Thread-6获得了信号量,时间为1444557042466
Thread-5获得了信号量,时间为1444557042466
Thread-8获得了信号量,时间为1444557042467
Thread-9释放了信号量,时间为1444557044467
Thread-6释放了信号量,时间为1444557044467
Thread-7释放了信号量,时间为1444557044467
Thread-5释放了信号量,时间为1444557044468
Thread-8释放了信号量,时间为1444557044468
前10行为一部分,运行的线程是1 2 0 3 4,看到时间差也都是代码约定的2秒;后10行为一部分,运行的线程是9 7 6 5 8,时间差也都是约定的2秒,这就体现出了Semaphore的作用了。
这种通过Semaphore控制并发并发数的方式和通过控制线程数来控制并发数的方式相比,粒度更小,因为Semaphore可以通过acquire方法和release方法来控制代码块的并发数。
最后注意两点:
1、Semaphore可以指定公平锁还是非公平锁
2、acquire方法和release方法是可以有参数的,表示获取/返还的信号量个数
Exchanger
Exchanger,从名字上理解就是交换。Exchanger用于在两个线程之间进行数据交换,注意也只能在两个线程之间进行数据交换。线程会阻塞在Exchanger的exchange方法上,直到另外一个线程也到了同一个Exchanger的exchange方法时,二者进行数据交换,然后两个线程继续执行自身相关的代码。
Exchanger只有一个exchange方法,用于交换数据。看一下例子:
public static class ExchangerThread extends Thread
{
private String str;
private Exchanger<String> exchanger;
private int sleepSecond; public ExchangerThread(String str, Exchanger<String> exchanger, int sleepSecond)
{
this.str = str;
this.exchanger = exchanger;
this.sleepSecond = sleepSecond;
} public void run()
{
try
{
System.out.println(this.getName() + "启动, 原数据为" + str + ", 时间为" + System.currentTimeMillis());
Thread.sleep(sleepSecond * 1000);
str = exchanger.exchange(str);
System.out.println(this.getName() + "交换了数据, 交换后的数据为" + str + ", 时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
} public static void main(String[] args)
{
Exchanger<String> exchanger = new Exchanger<String>();
ExchangerThread et0 = new ExchangerThread("111", exchanger, 3);
ExchangerThread et1 = new ExchangerThread("222", exchanger, 2);
et0.start();
et1.start();
}
看一下运行结果:
Thread-0启动, 原数据为111, 时间为1444560972303
Thread-1启动, 原数据为222, 时间为1444560972303
Thread-0交换了数据, 交换后的数据为222, 时间为1444560975303
Thread-1交换了数据, 交换后的数据为111, 时间为1444560975303
看到两个线程交换了数据,由于一个线程睡2秒、一个线程睡3秒,既然要交换数据,肯定是睡2秒的要等待睡3秒的,所以看到时间差是3000ms即3s。
从这个例子看来,Exchanger有点像之前Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型一文中的SynchronousQueue的双向形式,它可能在遗传算法和管道设计中很有用。
Java多线程20:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger的更多相关文章
- java 22 - 20 多线程之线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互. 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池. 线程池里的每一个线程代码结束后 ...
- Java多线程(十五):CountDownLatch,Semaphore,Exchanger,CyclicBarrier,Callable和Future
CountDownLatch CountDownLatch用来使一个线程或多个线程等待到其他线程完成.CountDownLatch有个初始值count,await方法会阻塞线程,直到通过countDo ...
- Java多线程21:多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask
CyclicBarrier 接着讲多线程下的其他组件,第一个要讲的就是CyclicBarrier.CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到 ...
- java 多线程 30: 多线程组件之 CyclicBarrier
CyclicBarrier 接着讲多线程下的其他组件,第一个要讲的就是CyclicBarrier.CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到 ...
- 【Java EE 学习 22 下】【单线程下载】【单线程断点下载】【多线程下载】
一.文件下载简述 1.使用浏览器从网页上下载文件,Servlet需要增加一些响应头信息 (1)response.setContentType("application/force-downl ...
- java并发与多线程面试题与问题集合
http://www.importnew.com/12773.html https://blog.csdn.net/u011163372/article/details/73995897 ...
- [转]Java中的多线程你只要看这一篇就够了
如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...
- Java面试09|多线程
1.假如有Thread1.Thread2.Thread3.Thread4四条线程分别统计C.D.E.F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现? 把相互独立的计算任 ...
- Java基础之多线程框架
一.进程与线程的区别 1.定义: 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比 ...
随机推荐
- MySQL之运算符与函数、自定义函数
一自定义函数简介 (1)自定义函数定义 用户自定义函数(user-defined function,UDF)是一种对MySQL数据库扩展的途径,其用法与内置函数相同. (2)自定义函数的两个必要条件 ...
- discuz二次开发,分析和实现 之 向dz数据库插入自己的帖子吧
发个博客太麻烦了,难怪写博客的越来越少,吐一下,cnblogs的编辑器模板太丑! 最近开发社区 需要采集一些数据,使得模板输出有图文效果.就写了个简单的采集脚本,爬取目标站的内容,(用php 下载图片 ...
- Spring 4 官方文档学习(十三)集成其他web框架
重点是通用配置,非常建议看一下!有助于理解Spring的ApplicationContext与Servlet Container的关系! 1.介绍 Spring Web Flow SWF目标是成为we ...
- 理解soft-clipped reads
什么是soft-clipped reads 当基因组发生某一段的缺失,或转录组的剪接,在测序过程中,横跨缺失位点及剪接位点的reads回帖到基因组时,一条reads被切成两段,匹配到不同的区域,这样的 ...
- Linux下的虚拟Bridge实现
http://www.cnblogs.com/zmkeil/archive/2013/04/21/3034733.html Linux下的Bridge也是一种虚拟设备,这多少和vlan有点相似,它依赖 ...
- RFC2119:表示要求的动词(转)
RFC(Request For Comments)指的关于互联网标准的正式文件,它们的内容必须写得非常清楚. 表达的时候,必须严格区分哪些是"建议"(suggestion),哪些是 ...
- 虚拟机上安装ArchLinux笔记
安装前的自白: 想使用ArchLinux,就直接在虚拟机上先装一个玩起来先.虚拟机使用的是Vmware,下载免费的个人版本就可以了. Arch Linux的版本为2016.4.1 内核为4.4.5 在 ...
- eclipse/myeclipse sublime 实时更新文件改变
情形: 在使用eclipse/myeclipse开发的时候, 像JS 或者HTML 以及一些操作时,sublime 的效率比eclipse/myeclipse要快,所以我们就可以使用这两者一起开发. ...
- Integrate non-OSGi Dependencies
Consider a scenario where you need to use a non-OSGi jar in your OSGi project. Most libraries are no ...
- Alice and Bob(2013年山东省第四届ACM大学生程序设计竞赛)
Alice and Bob Time Limit: 1000ms Memory limit: 65536K 题目描述 Alice and Bob like playing games very m ...