http://pluto418.iteye.com/blog/1179497
1. 前言
JDK提供的并发包,除了上一篇提到的用于集合外,还有线程的调度、协作、调度等等功能。上篇提到过,线程之间除了竞争关系,还有协作关系。在高并发环境下有效利用Java并发包解决线程之间协作的特殊场景。在并行计算,尤其是多线程计算的结果集合并的时候都需要用到这些并发同步器。还有一种使用场景,就是跨越多台机器(实机)的多线程进行并行运算,需要将多台机器进行结果集的汇总,合并。其原理核心也是使用这些并发协作包。
2. FutureTask
FutureTask是进行并行结果集合并的类,此类是Future接口的实现。在主线程中启动多个线程进行并发计算,之后再根据各个线程的执行结果进行汇总,归并,得出一个总的结果,这个多线程可以是在一台机器上,充分利用多核CPU硬件,在科研单位可能分布式集群环境一起并发计算一个大任务,每个机器相当于一个线程,执行完毕后将反馈的结果返回来进行合并后才是最终的结果。而主线程可以等待分线程的结果,也可以不等待,全凭具体业务需要而定,不过一般情况下还是要等一等分线程的结果才能往下执行的。如果不等分线程,也可以在主线程中不再理会分线程即可。
举个实例,比如这时候东方不败要想练成《葵花宝典》,那么需要前提条件是2个,第一手中得有《葵花宝典》秘籍,第二就是挥刀自宫。恩,挥刀自宫这个主线程——东方不败可以自己完成,夺取《葵花宝典》可以派别人——兄弟童柏雄去干,2条线并行实施,等另一个人取得《葵花宝典》了,这边主线程也挥刀自宫了,行了,能练了!
咱先看代码行吧
- package threadConcurrent.test;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.FutureTask;
- /**
- * 分线程汇总
- * @author liuyan
- */
- public class FutureTaskDemo {
- @SuppressWarnings("unchecked")
- public static void main(String[] args) {
- // 初始化一个Callable对象和FutureTask对象
- Callable otherPerson = new OtherPerson();
- // 由此任务去执行
- FutureTask futureTask = new FutureTask(otherPerson);
- // 使用futureTask创建一个线程
- Thread newhread = new Thread(futureTask);
- System.out.println("newhread线程现在开始启动,启动时间为:" + System.nanoTime()
- + " 纳秒");
- newhread.start();
- System.out.println("主线程——东方不败,开始执行其他任务");
- System.out.println("东方不败开始准备小刀,消毒...");
- //兄弟线程在后台的计算线程是否完成,如果未完成则等待
- //阻塞
- while (!futureTask.isDone()) {
- try {
- Thread.sleep(500);
- System.out.println("东方不败:“等兄弟回来了,我就和小弟弟告别……颤抖……”");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println("newhread线程执行完毕,此时时间为" + System.nanoTime());
- String result = null;
- try {
- result = (String) futureTask.get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- if("OtherPerson:::经过一番厮杀取得《葵花宝典》".equals(result)){
- System.out.println("兄弟,干得好,我挥刀自宫了啊!");
- }else{
- System.out.println("还好我没自宫!否则白白牺牲了……");
- }
- }
- }
- @SuppressWarnings("all")
- class OtherPerson implements Callable {
- @Override
- public Object call() throws Exception {
- // 先休息休息再拼命去!
- Thread.sleep(5000);
- String result = "OtherPerson:::经过一番厮杀取得《葵花宝典》";
- System.out.println(result);
- return result;
- }
- }
在这个例子中主线程代表东方不败,分线程代表兄弟——童柏雄,主线程派出FutureTask,把它放置于一个线程对象中,之后线程开始启动,分支线程开始工作。主线程也没闲着,继续做自己的事情,消毒,做着心理斗争等等,通过一个阻塞的死循环,等待分线程的状态,调用分线程的futureTask.isDone()方法进行判断,看看兄弟是否执行结束了,结束了通过futureTask.get()将分线程的执行结果取出来,结果出来了,主线程根据分线程的执行结果再做决定。执行后,结果大家都明了。有一点需要说明的是,有可能分线程与主线程的执行不在一台物理机器上,分线程可以使用jms、webservic、rmi甚至socket技术请求远程的类为其服务。分线程根据远程返回的结果再返回给本机器的主线程,之后再做决策。分布式计算的核心原理也是如此,当然分布式计算比这个复杂得多,笔者只是说其核心的实现原理。
3. Semaphore
Semaphore是限制多线程共享资源的一个东东,多线程并发访问一个资源的时候,可以限制线程最大使用个数,其他多出来的线程,没办法,耐心等着吧。这个例子在生活中比比皆是,在火车站售票处一共开设了5个窗口,也就表示在同一时间内,火车站的工作人员最多只能为5个人服务,那么高峰时其他人呢,理想的情况下是排队等着,不理想的情况下是,等待的队列没有秩序,有的只是拳头和权势,没有办法,人家的爸爸是李刚,撞人都没事何况是排队买票了,人家说的就是王法。当然了,这个咱们看具体程序。
- package threadConcurrent.test;
- import java.util.Random;
- import java.util.concurrent.Semaphore;
- /**
- * 使用Semaphore,限制可以执行的线程数,空闲资源放到队列中等待
- *
- * @author liuyan
- */
- public class SemaphoreDemo {
- public static void main(String[] args) {
- Runnable limitedCall = new Runnable() {
- // 随机生成数
- final Random rand = new Random();
- // 限制只有3个资源是活动的,第二个参数为true则是按照标准“队列”结构先进先出
- final Semaphore available = new Semaphore(5, true);
- int count = 0;
- public void run() {
- int time = rand.nextInt(10);
- int num = count++;
- try {
- // 请求资源
- available.acquire();
- int needTime = time * 2000;
- System.out.println("乘客" + num + "买票需要[ " + needTime
- + " 秒]... #");
- Thread.sleep(needTime);
- System.out.println("乘客" + num + "买完了 # !");
- // 运行完了就释放
- available.release();
- } catch (InterruptedException intEx) {
- intEx.printStackTrace();
- }
- }
- };
- for (int i = 0; i < 25; i++)
- new Thread(limitedCall).start();
- }
- }
注释已经写得比较明确了,构建Semaphore的时候,第一个参数代表线程的执行的最大数目,第二个参数是按照队列的形式将未执行的线程放到队列中,当有线程执行完了后,按照先进先出的原则,进行线程的唤醒,执行。即便是main启动了25个线程,那么其余的线程要向执行也要等前面的线程执行完毕后才能有资格执行。要想让线程按规矩执行,首先应该先向资源池申请资源,available.acquire();就是请求资源池给个资源,如果资源池当前有空闲资源,那么线程就可以正常运行了,如果没有,没办法,排队吧啊。线程运行完毕了,要记得归还资源available.release();如果构造Semaphore的时候没指定第二个参数,或者第二个参数为false,估计您有幸能见到我之前说的李刚的儿子的现象!在此不再赘述。
4. ScheduledFuture
提到Quartz,大家都知道他是一个负责任务调度的开源工具,使用它可以轻易地在某一时段,某一频率执行相关业务功能。如果仅仅是简单的根据某些时间频率执行某些任务,其实到不必屠龙刀杀小鸡,使用ScheduledFuture可以轻松解决此类频率的问题,启动另一个线程来,在某一个时间频率执行代码。这个还是举个例子吧,战争年代巡视城防,赵云带一个小兵去巡视城防,赵云是将军,每5秒钟巡视一次士兵,看看士兵有没有偷懒,士兵比较累,每1秒巡视一次城防,不能睡觉。如下程序
- package threadConcurrent.test;
- import static java.util.concurrent.TimeUnit.SECONDS;
- import java.util.Date;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ScheduledExecutorService;
- import java.util.concurrent.ScheduledFuture;
- /**
- * 时间频率调度
- * @author liuyan
- */
- public class ScheduledFutureDemo {
- @SuppressWarnings("unchecked")
- public static void main(String[] args) {
- // 线程池开辟2个线程
- final ScheduledExecutorService scheduler = Executors
- .newScheduledThreadPool(2);
- // 将军
- final Runnable general = new Runnable() {
- int count = 0;
- public void run() {
- System.out.println(Thread.currentThread().getName() + ":"
- + new Date() + "赵云巡视来了 " + (++count));
- }
- };
- // 士兵
- final Runnable soldier = new Runnable() {
- int count = 0;
- public void run() {
- System.out.println(Thread.currentThread().getName() + ":"
- + new Date() + "士兵巡视来了 " + (++count));
- }
- };
- // 1秒钟后运行,并每隔2秒运行一次
- final ScheduledFuture beeperHandle1 = scheduler.scheduleAtFixedRate(
- soldier, 1, 1, SECONDS);
- // 5秒钟后运行,并每隔2秒运行一次
- final ScheduledFuture beeperHandle2 = scheduler.scheduleWithFixedDelay(
- general, 5, 5, SECONDS);
- // 30秒后结束关闭任务,并且关闭Scheduler
- scheduler.schedule(new Runnable() {
- public void run() {
- beeperHandle1.cancel(true);
- beeperHandle2.cancel(true);
- scheduler.shutdown();
- }
- }, 60, SECONDS);
- }
- }
5. CountDownLatch
很多资料上都说CountDownLatch是倒数计数器,我觉得这种说法太过专业,其实它就是一个数数的人员。利用它,可以在多线程执行任务完毕后完成进行多线程的等待,便于等待所有的线程之后在干别的事情,这个有点类似于FutureTask,使用上不太一样。这个场景就是一个线程必须要等到其他线程执行完毕后才能往下执行,注意,这里这个线程没必要需要其他线程的执行结果,而是只有一个条件就是其他线程必须执行完毕。咱们依然做个比喻,韦小宝需要8部《四十二章经》才能去鹿鼎山找寻宝藏,怎么办,他有7个老婆,不能资源浪费啊,这个任务同时进行吧。咱们设计8个线程同时进行,等所有老婆都执行完毕了,每个老婆找齐了《四十二章经》了,好了,他可以自己去找宝藏了。等等,咱们小宝哥有7个老婆,何来8个线程,这个问题,读者不必较真,举个例子罢了,咱们给他加个老婆不就行了!
代码如下
- package threadConcurrent.test;
- import java.util.concurrent.CountDownLatch;
- /**
- * 分部执行任务
- *
- * @author liuyan
- *
- */
- public class CountDownLatchDemo implements Runnable {
- private int id;
- // 线程之间不影响,到了终点后继续做自己的事情
- private CountDownLatch countDownLatch;
- public CountDownLatchDemo(int id, CountDownLatch countDownLatch) {
- this.id = id;
- this.countDownLatch = countDownLatch;
- }
- @SuppressWarnings("static-access")
- @Override
- public void run() {
- try {
- System.out.println("第" + (id + 1) + "小老婆开始查找《四十二章经》...");
- Thread.currentThread().sleep(id * 1000);
- System.out.println("第" + (id + 1) + "本《四十二章经》找到");
- //计数器将等待数字-1
- countDownLatch.countDown();
- System.out.println("第" + (id + 1) + "小老婆继续干其他事情");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- CountDownLatch countDownLatch = new CountDownLatch(8);
- for (int i = 0; i < 8; i++) {
- new Thread(new CountDownLatchDemo(i, countDownLatch)).start();
- }
- try {
- System.out.println("韦小宝等着等着8本四十二章……");
- // 韦小宝等着等着
- countDownLatch.await();
- // 等待运动员到达终点
- System.out.println("8本四十二章经找寻完成,可以寻宝了!");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
主线程就当做是韦小宝吧,主线程首先开辟了线程计数器对象,之后就开辟了8个线程,派出了8个小老婆去办事,之后主线程调用countDownLatch.await()阻塞,在家里一直喝着小茶,听着小曲等着8个线程的执行完毕。咱们来看小老婆们,小老婆们就辛苦了,开始找寻经书,之后调用countDownLatch.countDown()方法通知线程计数器减去1,让等待的线程减去1。就好比说有个小老婆找到了《四十二章经》,用iphone发个彩信将经书的夹缝地图发给韦小宝,韦小宝收到了,恩,这个老婆能干,任务完成,我的等待目标减去1。等到等待线程为0的时候,小宝开始行动了,将手机里的地图通过游戏——拼图游戏,一拼凑,大事可成,自己寻宝去!这里大家也看到了CountDownLatch与FutureTask的区别。CountDownLatch侧重的是分线程的完成个数,每次完成一个分线程,等待数目减少一个,等待线程为0的时候,主线程的就不阻塞了,开始往下走。而分线程一旦调用countDownLatch.countDown()方法,就代表分线程任务搞定,主线程就不会因为你的其他事情而不能往下走,完成任务了,小老婆们也可以去旅旅游,休息休息!而FutureTask则是注重执行结果的,主线程需要它的确切结果。所以futureTask执行的call()有返回值。
6. CyclicBarrier
CyclicBarrier相对于CountDownLatch来说,最大的不同是,分线程具体的执行过程受其他分线程的影响,必须每个分线程都执行完毕了,主线程才继续往下走,而分线程如果在所有分线程执行完毕后还有其他动作,ok,还你自由,不必阻塞了,往下走你的路吧。这个例子是网上的游戏玩家的例子,4个小孩玩游戏,游戏要求必须是4个小孩都得通过第一关,才能开启第二关的关口!否则其他完成第一关的人都得等着其他人完成。这个有点像我们的项目开发,分模块开发,到一定阶段将模块汇总,联调,测试,如果这时候有一个模块没完成,大家等着吧,大家都在那里静静地、盯着你、等着你。
- package threadConcurrent.test;
- import java.util.concurrent.BrokenBarrierException;
- import java.util.concurrent.CyclicBarrier;
- public class GameBarrier {
- public static void main(String[] args) {
- CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
- @Override
- public void run() {
- System.out.println("所有玩家进入第二关!");
- }
- });
- for (int i = 0; i < 4; i++) {
- new Thread(new Player(i, cyclicBarrier)).start();
- }
- }
- }
- class Player implements Runnable {
- /**
- * 线程之间需要交互,到一定的条件下,所有线程才能往下走
- */
- private CyclicBarrier cyclicBarrier;
- private int id;
- public Player(int id, CyclicBarrier cyclicBarrier) {
- this.cyclicBarrier = cyclicBarrier;
- this.id = id;
- }
- @Override
- public void run() {
- try {
- System.out.println("玩家" + id + "正在玩第一关...");
- cyclicBarrier.await();
- System.out.println("玩家" + id + "进入第二关...");
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- }
使用cyclicBarrier.await();方法进行等待、阻塞,当所有分线程执行完毕了,主线程开始执行,分线程的自由也解脱了,继续往下走,开始第二关。
7. Exchanger
Exchanger是线程资源交换器,线程A与线程B在某个运行阶段需要互换资源才能完成任务。这就好比2个公司职员——叶小钗和一页书。分别在不同的项目组——组A和组B,两个组开发者不同的项目,在正常时候叶小钗在组A上班开发者BOSS项目,一页书在项目组B开发ESB中间件产品。而在特殊时期项目组B不需要一页书了,需要叶小钗提供技术支持,就和项目组A要叶小钗,项目组A的leader也不是吃素的,你要叶小钗,没问题,把一页书也得接我们项目组剥削几天!就这样项目组B与项目组A做了这种“交易”(交换),用完了之后,恩~看程序吧
- package threadConcurrent.test;
- import java.util.concurrent.Exchanger;
- /**
- * 资源交换
- * @author liuyan
- */
- public class ExgrDemo {
- public static void main(String args[]) {
- //交换器
- Exchanger<String> exgr = new Exchanger<String>();
- new TeamB(exgr);
- new TeamA(exgr);
- }
- }
- /**
- * 项目组A
- * @author liuyan
- */
- class TeamA implements Runnable {
- Exchanger<String> ex;
- String str;
- TeamA(Exchanger<String> c) {
- ex = c;
- str = new String();
- new Thread(this).start();
- }
- public void run() {
- char ch = 'A';
- for (int i = 0; i < 3; i++) {
- for (int j = 0; j < 5; j++)
- str += (char) ch++;
- try {
- str = ex.exchange(str);
- } catch (InterruptedException exc) {
- System.out.println(exc);
- }
- }
- }
- }
- /**
- * 项目组B
- * @author liuyan
- */
- class TeamB implements Runnable {
- Exchanger<String> ex;
- String str;
- TeamB(Exchanger<String> c) {
- ex = c;
- new Thread(this).start();
- }
- public void run() {
- for (int i = 0; i < 3; i++) {
- try {
- str = ex.exchange(new String());
- System.out.println("Got: " + str);
- } catch (InterruptedException exc) {
- System.out.println(exc);
- }
- }
- }
- }
需要说明的就是这种交换一定是成对儿的,就是交换的线程数目一定是偶数。否则奇数个线程,剩下那一个和谁交换去?这也是“等价交易”的一种体现。
8. 总结
这次主要介绍了并发环境下常常使用的并发包,用于控制多线程的并发调度、同步、交互、交换、协作等等。使用这些协作同步器,可以更灵活的处理线程之间的关系。也能更好地使用硬件资源为我们的并发系统提供高效率的运行能力。当然这次总结仅仅限于使用的层次,底层的实现源码分析有时间再做总结。
转载:http://suhuanzheng7784877.iteye.com/blog/1145286
相关推荐
通过合理利用C#提供的线程同步机制,如`Monitor`类,以及理解并避免常见的多线程陷阱,如死锁和竞态条件,开发者可以构建出高效且稳定的多线程应用。在实际开发中,还应考虑使用更现代的并发编程模式,如`Task`和`...
这里,我们主要探讨的是如何通过编写多线程并发程序来优化应用程序的性能,提高系统的处理能力。 首先,多线程是指在一个进程中同时执行多个线程。线程是操作系统调度的基本单位,它允许程序同时执行多个任务。多...
多线程同步机制在软件开发中扮演着至关重要的角色,特别是在多处理器系统或者并发执行的任务中,确保线程间的正确协作和数据一致性是必不可少的。VC++中提供了多种同步机制来处理多线程间的同步问题,其中Event是...
在Java编程中,多线程并发是...总之,Java的多线程并发实例可以帮助我们更好地理解和实践线程控制、同步机制以及经典的设计模式,提升我们的编程能力。通过不断学习和实践,我们可以编写出高效、安全的多线程并发程序。
总之,Java的多线程和并发编程是一个复杂而重要的主题,它涉及到操作系统原理、JVM行为、线程管理、同步机制等多个方面,熟练掌握这些知识对于开发高效、可靠的Java应用程序至关重要。通过理解线程的工作原理和使用...
本文作者通过具体实现了一个并发同步框架的例子来展示了如何利用Java多线程机制解决实际问题。该框架充分利用了Java的多线程机制,如`synchronized`关键字、`volatile`变量等来保证数据一致性,并通过合理的锁设计和...
在Linux操作系统中,多线程并发控制是一个至关重要的主题,特别是在设备驱动编程和系统级开发中。本文将深入探讨Linux下并发控制的原因、方法以及具体的实现机制。 并发控制的主要原因是由于多个线程可能同时访问...
基于Java并发编程的多线程同步与锁机制 项目简介 本项目旨在深入探讨Java并发编程中的多线程同步与锁机制,涵盖了从基础的线程创建、同步方法到高级的并发工具类如ReentrantLock、ReadWriteLock、Atomic类等的...
WEB API 多线程并发测试工具”重复了标题的信息,强调了该工具的核心功能,即为Web API提供多线程并发的测试环境,确保在高负载情况下的系统表现。 **API测试工具** API测试工具是用于验证API功能、性能和安全性...
使用场景及目标:帮助读者掌握C语言中的多线程编程技术和常用同步机制,能够在开发高性能并发应用程序时合理运用这些技术,避免常见的线程安全问题和死锁情况。 阅读建议:结合实际编程项目和调试工具练习相关知识点...
在处理大量图像或需要快速响应时间的应用场景中,多线程并发识别可以显著提升效率。以下将详细介绍如何利用Tesseract OCR实现多线程并发识别,以及可能涉及的相关技术点。 首先,理解Tesseract OCR的基本工作原理是...
总之,C#的多线程并发HTTP请求机制结合HttpWebRequest类,为开发高效网络爬虫提供了强大的工具。通过良好的设计和优化,可以构建出能够处理大量并发请求的采集程序,同时确保对目标网站的影响最小化。在实际应用中,...
Java多线程与并发编程是Java语言中用于处理多任务执行的关键技术,它能够帮助开发者设计出能够有效应对高并发请求的应用程序。在现代的线上(Online)和离线(Offline)应用中,合理利用多线程技术可以大幅提高系统...
在编程领域,多线程并发处理是一种常见的优化技术,它能充分利用多核处理器的资源,提高程序的执行效率。...正确地使用多线程并发,不仅可以提高程序性能,还能简化同步和锁的管理,降低程序的复杂性。
### Java多线程并发知识点详解 #### 一、Java多线程并发简介 在现代软件开发中,特别是在Java这样的主流编程语言中,多线程并发技术是提高程序执行效率、优化资源利用的关键手段之一。本篇文章将深入探讨Java中的...
同步机制是处理多线程间数据安全的关键。Java提供了多种同步工具,如`synchronized`关键字、`Lock`接口(如`ReentrantLock`)、`Semaphore`信号量、`CountDownLatch`计数器和`CyclicBarrier`回环栅栏。这些工具用于...
本文将深入探讨“模拟摄像头libuv支持多线程并发”这一主题,结合“模拟IPC”这一标签,以及文件名称“IpcSimulate”,我们将分析如何利用libuv库在多线程环境下实现模拟摄像头的功能。 首先,让我们了解一下什么是...
### 并发、多线程、同步异步概念解析 #### 一、并发与多线程 **并发**(Concurrency)是指多个任务看起来同时进行的一种现象。在计算机领域,特别是操作系统层面,它指的是一个时间段内有多个程序或任务都在运行...
Python 多线程的同步机制 在Python编程中,多线程是实现并发处理的一种方式,特别是在处理大量数据或执行耗时操作时。然而,多线程环境下常常会出现数据共享的问题,这可能导致数据的不一致性和错误。为了解决这个...