我们之前学习创建线程有Thread和Runnable两种方式,但是两种方式都无法获得执行的结果。
而Callable和Future在任务完成后得到结果。
 
Future是一个接口,表示一个任务的周期,并提供了相应的方法来判断是否已经完成,以及获取任务的结果和取消任务。
 
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
 
Future接口
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; @Slf4j
public class FutureExample { static class MyCallable implements Callable<String> { @Override
public String call() throws Exception {
log.info("do something in callable");
Thread.sleep();
return "Done";
}
} public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new MyCallable());
log.info("do something in main");
//Thread.sleep(10000);
log.info("这里不阻塞,可以继续异步执行");
String result = future.get(); //get方法会发生阻塞,如果判断任务是否执行完成使用isDone()方法
log.info("result:{}", result);
}
}

 FutureTask

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; @Slf4j
public class FutureTaskExample { public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
log.info("do something in callable");
Thread.sleep();
return "Done";
}
}); new Thread(futureTask).start();
log.info("do something in main");
Thread.sleep();
String result = futureTask.get();
log.info("result:{}", result);
}
}

Fork/Join

用于并行执行任务的框架,将大任务分割成小任务,最终将每个小任务的结果汇总得到大任务结果的框架。

思想和map/reduce非常像,Fork就是讲大任务分割成小任务,Join就是合并子任务的结果。

工作窃取算法

工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。工作窃取的运行流程图如下:

那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。

Fork/Join使用两个类来完成以上两件事情:

ForkJoinPool :它负责实现,包括我们的工作窃取算法,它管理工作线程和任务状态以及执行信息

ForkJoinTask:主要提供fork和join的机制

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask; @Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> { public static final int threshold = ;
private int start;
private int end; public ForkJoinTaskExample(int start, int end) {
this.start = start;
this.end = end;
} @Override
protected Integer compute() {
int sum = ; //如果任务足够小就计算任务
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任务大于阈值,就分裂成两个子任务计算
int middle = (start + end) / ;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + , end); // 执行子任务
leftTask.fork();
rightTask.fork(); // 等待任务执行结束合并其结果
int leftResult = leftTask.join();
int rightResult = rightTask.join(); // 合并子任务
sum = leftResult + rightResult;
}
return sum;
} public static void main(String[] args) {
ForkJoinPool forkjoinPool = new ForkJoinPool(); //生成一个计算任务,计算1+2+3+4
ForkJoinTaskExample task = new ForkJoinTaskExample(, ); //执行一个任务
Future<Integer> result = forkjoinPool.submit(task); try {
log.info("result:{}", result.get());
} catch (Exception e) {
log.error("exception", e);
}
}
}

 BlockingQueue

在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。
认识BlockingQueue

BlockingQueue是一种数据结构,支持一个线程往里存资源,另一个线程从里取资源。这正是解决生产者消费者问题所需要的

      多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。然而,在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。好在此时,强大的concurrent包横空出世了,而他也给我们带来了强大的BlockingQueue。(在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒)
下面两幅图演示了BlockingQueue的两个常见阻塞场景:
  
如上图所示:当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。

如上图所示:当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。

1. ArrayBlockingQueue

基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
  ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue

2. LinkedBlockingQueue

基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。

使用阻塞队列解决生产者-消费者问题

FutureTask、Fork/Join、 BlockingQueue的更多相关文章

  1. 一文带你了解J.U.C的FutureTask、Fork/Join框架和BlockingQueue

    摘要: J.U.C是Java并发编程中非常重要的工具包,今天,我们就来着重讲讲J.U.C里面的FutureTask.Fork/Join框架和BlockingQueue. 本文分享自华为云社区<[ ...

  2. 【转】mysql的union、left join、 right join、 inner join和视图学习

    1.联合 union 进行多个查询语句时,要求多次查询的结果列数必须一样.此时,查询的结果以第一个sql语句的列名为准且union会自动去重复我们应该使用union all. 例...... 1.联合 ...

  3. sql之left join、right join、inner join的区别

    sql之left join.right join.inner join的区别 left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) 返回包括 ...

  4. SQL表连接查询(inner join、full join、left join、right join)

    SQL表连接查询(inner join.full join.left join.right join) 前提条件:假设有两个表,一个是学生表,一个是学生成绩表. 表的数据有: 一.内连接-inner ...

  5. SQL的inner join、left join、right join、full outer join、union、union all

    主题: SQL的inner join.left join.right join.full outer join.union.union all的学习. Table A和Table B表如下所示: 表A ...

  6. mysql not in、left join、IS NULL、NOT EXISTS 效率问题记录

    原文:mysql not in.left join.IS NULL.NOT EXISTS 效率问题记录 mysql not in.left join.IS NULL.NOT EXISTS 效率问题记录 ...

  7. inner join、left join、right join中where和and的作用

    inner join.left join.right join中where和and的作用 .内连接(自然连接): 只有两个表相匹配的行才能在结果集中出现 2.外连接: 包括  (1)左外连接 (左边的 ...

  8. sql的left join 、right join 、inner join之间的区别

    sql中left join .right join .inner join之间的区别 left join (左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 : right join (右 ...

  9. SQL中关于Join、Inner Join、Left Join、Right Join、Full Join、On、 Where区别

    前言: 今天主要的内容是要讲解SQL中关于Join.Inner Join.Left Join.Right Join.Full Join.On. Where区别和用法,不用我说其实前面的这些基本SQL语 ...

随机推荐

  1. J2EE是什么?

    解答:从整体上讲,J2EE是使用Java技术开发企业级应用的工业标准,它是Java技术不断适应和促进企业级应用过程中的产物.适用于企业级应用的J2EE,提供一个平台独立的.可移植的.多用户的.安全的和 ...

  2. 第5步:建立主机间的信任关系(sgdb1、sgdb2)

    5.1 Oracle用户下建立信任 5.11创建.ssh目录 [root@sgdb1 /]# su - oracle [oracle@sgdb1 ~]$ mkdir .ssh       创建一个.s ...

  3. 房间WIFI信号不好怎么办?——无线路由桥接(WDS)

    背景 1.无线路由在客厅,房间的WIFI信号很差,只有1-2格,虽说是100M的网速,但是从客厅到房间要经过3道墙!电脑的无线接收功能一般都很一般,网速不好理所当然. 2.弄一根网线从客厅连接起来很傻 ...

  4. Laravel5.1 分页展示

    Laravel为我们提供了一套分页的逻辑,我们无需自己实现分页逻辑,只需要执行几个简单的方法就能实现漂亮的分页. 1 simplePaginate 这是一种只显示上一页下一页的样式分页,我们来看看怎么 ...

  5. C/C++程序内存分配详解

    一.常见的几个区 1.栈区(stack)程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈.程序结束时由编译器自动释放. 2.堆区(heap)在内存开辟另一块 ...

  6. Adapter适配器 final int Id 导致选中的Item不在当前界面

    写了上面这么一个横向混动,点击切换到,哪个的Item上就会有一个  常用  的小图标.但是我每次滑动切换到后面   成龙9这个Item,这个 常用的图片,也在 这个上面了,但是他一更新,就变成 等你再 ...

  7. iOS UITextView 展示html

    NSString *htmlString = @"<h1>Header</h1><h2>Subheader</h2><p>Some ...

  8. ubuntu16.04搭建jdk1.8运行环境

    搭建环境:Ubuntu 16.04 ×64 JDK :jdk-8u171-linux-x64.tar.gz 首先下载linux对应的安装包下载地址:http://www.oracle.com/tech ...

  9. php 代替 js实现自定义时间选择器

    w 0-php来结合服务端数据,处理前端页面的交互: 1-php 加法 乘法的效率对比,对算术运算的优化. 减少乘法运算? function w_select($wname, $wii) { $w = ...

  10. IO流入门-第四章-FileReader

    FileReader基本用法和方法示例 /* java.io.Reader java.io.InputStreamReader 转换流(字节输入流---->字符输入流) java.io.File ...