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调度和分派的基本单位,它是比 ...
随机推荐
- linux服务器apache 一个IP,一个端口,建立多个网站的方法。
找到apache-tomcat-6.0.14\conf\server.xml ,再services 后面添加此段代码: Xml代码 <!-- 此处 新增的项目配置--> <Ser ...
- 20151214study
An important quality of steel is its strength. (1)钢铁的最重要品质是其强度.She made a quick decision.她做了一个很快的决定. ...
- IP地址子网掩码主机地址网络号主机号
(1)子网掩码1所对应的位为网络号位 而所对应的位为主机号位 IP地址+子网掩码=网络号: IP地址+子网掩码(反码)=主机号. (2)主机号中的m位被用来表示网络号了,也就是子网号,将0-255划分 ...
- Select语句也会引起死锁
项目上线,准备验收前出现了一个严重的问题:很多select语句作为死锁的牺牲,大部分报表无法打开.这个问题影响范围很大所有的报表都无法访问,而我们的报表是放在电视上面轮播的,电视放在工厂里面,所以出现 ...
- [修改后]html+css 做成一个可浏览的表格
现在表格内容需要显示的要求如下: 1, 表格很大,界面放不小,需要放到div中. 2, 在div中可以用scroll滑动查看. 3, td中的内容保持在一行中. 4, 可以点击tr,然后可以选中并了解 ...
- Linux(Ubuntu 14.0)
开始了Mono的学习.学习了Mono for Android之后,编译一些小的APK,总发现这些APK文件很大,额,真心不知道为什么,那么,就让我们从头开始学期了,Android是基于Linux的,那 ...
- 面向对象的OOA、OOD、OOP
OOA Object-Oriented Analysis:面向对象分析方法 是在一个系统的开发过程中进行了系统业务调查以后,按照面向对象的思想来分析问题.OOA与结构化分析有较大的区别.OOA所强调的 ...
- doxygen的使用(二)给代码添加javadoc风格的注释
原创文章,欢迎阅读,禁止转载.本文记一下javadoc风格注释的写法,这些特殊格式的注释称作标签.按照这种规范写的注释才能生成到文档中. 块注释的写法 /** * @brief 这个块注释 * dox ...
- Win7 64位 MinGW环境测试SDL2.0.3
下载MinGW版的文件 http://www.libsdl.org/release/SDL2-devel-2.0.3-mingw.tar.gz 解压放到mysys下面 运行Makefile mysys ...
- CodeForces 742A Arpa’s hard exam and Mehrdad’s naive cheat
题意:求1378 n次幂的最后一位. 析:两种方法,第一种,就是快速幂,第二种找循环节,也很好找,求一下前几个数就好. 代码如下: #pragma comment(linker, "/STA ...