个人博客网:https://wushaopei.github.io/    (你想要这里多有)

一、J.U.C-FutureTask-1

FutureTask组件,该组件是JUC中的。但该组件不是 AQS 的子类。

创建一个线程通常有两种方式,一种是直接继承Thread类,另一红就是实现Runnable接口,这俩种方式有一个共同的缺陷,那就是在完成任务之后无法获取执行结果。从Java1.5开始提供了CallableFutureTask,可以在任务完成之后得到任务执行的结果。

1、Callable 与 Runnable 接口对比:

Runnable的代码非常简单,它是一个接口,而且只有一个方法,那就是run(),实现它把一些业务的操作写在里面,然后使用某个线程去执行该Runnable实现类就可以实现多线程;

Callable的代码也非常简单,不同的是它是一个泛型的接口,有一个call 函数,call函数的类型就是我们传进去的类的类型。

Callable和Runnable的功能大致相似,Callable的功能更强大一些。主要是Callable在执行完后可以有返回值,并且抛出异常。

2、Future接口:

Future也是一个接口,对于我们定义的Callable或者Runnable定义的具体的任务,它可以取消。查询的任务是否被取消、查询的过程是否完成以及获取结果等等。

通常程序的线程都是异步执行的,所以通常需要可以直接从其他的线程中得到返回值。这个时候,Future就出场了。Future可以监视目标线程调用call()的情况,当你调用Future的get()方法时,就可以获得它的结果。通常这个时候,线程可能不会直接完成,当前线程就开始阻塞,知道call()方法结束返回结果,线程才继续执行。

总结: Future可以得到其他线程的任务方法的返回值。

3.FutureTask类:

FutureTask的父类是RunnableFuture,,而RunnableFuture实现了Runnable和Future两个接口;由此我们可以知道,FutureTask它最终执行的是callable类型的任务。

如果构造函数参数的类型是callable的话,它会转换成callable类型。

RunnableFuture实现了Runnable和Future两个接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

该组合FutureTask的使用的好处是?

假设有一个很费时的逻辑需要计算,并且需要返回这个值,同时这个值又不是马上需要,那么就可以使用这个组合。用另外一个线程去计算返回值,而当前线程在使用这个返回值之前做其他的操作,等到需要用到这个返回值的时候再通过Future得到。

二、J.U.C-FutureTask-2

1、代码演示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(5000);
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(1000);
String result = future.get();
log.info("resultï¼{}", result);
}
}

代码执行结果:

17:28:32.168 [main] INFO com.mmall.concurrency.example.aqs.FutureExample - do something in main
17:28:32.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.aqs.FutureExample - do something in callable
17:28:37.171 [main] INFO com.mmall.concurrency.example.aqs.FutureExample - resultï¼Done

由执行结果的时间可知,call()方法中打印do something in callable 即线程初始化执行的时间和main方法中执行打印do something in main的时间一直,可说明两者是异步同时执行的。

再由call()线程返回值的打印时间可知,它是在线程初始化以及main初始化5秒后才打印结果result,可以说明future一直被阻塞,直到call()方法中的业务执行完成并返回结果后才接着继续执行get()方法。

2、代码演示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(5000);
return "Done";
}
});
new Thread(futureTask).start();
log.info("do something in main");
Thread.sleep(1000);
String result = futureTask.get();
log.info("result:{}",result);
}
}

代码执行结果:

17:37:57.378 [Thread-0] INFO com.mmall.concurrency.example.aqs.FutureTaskExample - do something in callable
17:37:57.378 [main] INFO com.mmall.concurrency.example.aqs.FutureTaskExample - do something in main
17:38:02.384 [main] INFO com.mmall.concurrency.example.aqs.FutureTaskExample - result:Done Process finished with exit code 0

分析执行结果:

由执行结果的时间可知,call()方法中打印do something in callable 即线程初始化执行的时间和main方法中执行打印do something in main的时间一直,可说明两者是异步同时执行的。

再由call()线程返回值的打印时间可知,它是在线程初始化以及main初始化5秒后才打印结果result,可以说明futureTask 一直被阻塞,直到call()方法中的业务执行完成并返回结果后才接着继续执行get()方法。

3、对比Future和FutureTask执行声明与执行结果:

可以知道,FutureTask使用起来要更加方便,定义好任务之后,直接启动任务,什么时候想用,什么时候就可以用它。

4、部分源码分析:

由FutureTask的构造方法可以知道,它支持多种类型的构造函数的。

同时,当传入的是Runnable时,还可以指定返回值的类型

三、J.U.C-ForkJoin

JUC 里面的ForkJoin框架是JAVA7提供的一个用于实现并行任务的框架,它是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务的结果的框架

它的思想和Mapreduce的思想比较类似。

从字面上看,Fork就是把一个大任务切割成若干个子任务来并行执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的执行结果。它主要采用的是工作窃取算法。

工作窃取算法的意思是指某个线程从其他队列里窃取任务来执行。

1、工作窃取算法的工作流程图及原理:

从图中分析,该算法从线程1开始执行,然后到达任务4时,会在尾部进入到线程2去窃取属于线程2 的任务来执行。窃取线程的执行顺序自下而上,被窃取线程在执行时是自上而下

这里为什么要使用工作窃取算法呢?

假如我们需要做一个比较大的任务,我们可以把这个任务分割成若干个互不依赖的子任务。为了减少线程间的竞争,于是把这些子任务放到不同的队列里。为每个队列创建一个单独的线程来执行队列里的任务。线程和队列一一对应,比如A线程负责A队列里的任务。

但是,有的线程会先把自己线程里的任务干完。其他线程对应的队列里还有任务等待处理。干完活的线程与其光等着,还不如去帮助其他的线程干活。于是,它就去其他线程的队列里去窃取一个线程来执行,而在这时,它们会访问同一个队列,所以,为了减少窃取任务线程和被窃取任务线程之间的竞争,我们会使用双端队列。被窃取任务的线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部那任务执行。

优点:充分利用了线程进行并行计算,并减少线程之间的竞争。

缺点:某些情况下,还是存在竞争,比如,双端队列里只有一个任务时,同时执行会消耗更多的系统资源。比如,创建了多个线程和多个双端队列。

对于ForkJoin框架而言,当一个任务正在等待它使用join()操作创建的子任务结束时。执行这个任务的工作线程,它的其他未被执行的任务会开始它的执行。通过这种方式,线程充分利用他们的运行时间来提高应用程序的性能。

为了达到这个目标,ForkJoin框架有一些局限性。具体有:

任务始终只能够使用Fork()或Join()操作来作为同步机制。如果使用了为他同步机制,当那个它们在同步工作时,工作线程就不能执行任务了。比如:在ForkJoin框架中,你使任务进入了睡眠,这个睡眠期间内执行其他任务的工作线程将不能执行其他任务了。

第二个局限性,我们所拆分的任务不应该去执行IO操作,如读或写数据文件;

第三个局限性,任务不能抛出或检查异常,它必须通过bug的代码来处理它

ForkJoin框架的核心是两个类,ForkJoinPool和ForkJoinTask.,Pool负责来做实现,包括工作窃取算法,它管理工作线程和提供任务工作的状态和它们的执行信息。而ForkJoinTask主要负责在任务中执行Fork()或者Join()操作的机制。

2、代码演示ForkJoin的使用:

@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
public static final int threshold = 2;
private int start;
private int end; public ForkJoinTaskExample(int start, int end) {
this.start = start;
this.end = end;
} @Override
protected Integer compute() {
int sum = 0; //如果任务足够小就计算任务
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任务大于阈值,就分裂成两个子任务计算
int middle = (start + end) / 2;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, 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(1, 100); //执行一个任务
Future<Integer> result = forkjoinPool.submit(task); try {
log.info("result:{}", result.get());
} catch (Exception e) {
log.error("exception", e);
}
}
}

代码执行结果:

18:26:35.223 [main] INFO com.mmall.concurrency.example.aqs.ForkJoinTaskExample - result:5050

Process finished with exit code 0

四、J.U.C-BlockingQueue

BlockingQueue, 阻塞队列

1、BlockingQueue 的实现流程图及原理

BlockingQueue,在某些情况下,对该队列的访问,可能会造成阻塞。

被阻塞的情况主要有如下两种:

第一种:当队列满的时候,进行入队列操作;

第二种:当队列空的时候,进行出队列操作

当一个线程对一个满了的队列进行入队列操作的时候,它就会阻塞,除非有另一个线程做了出队列的操作。同样的,当一个线程对一个空的队列做出队列操作时,它也将被阻塞,除非有一个线程做了如队列操作。

阻塞队列是线程安全的,阻塞队列主要用作生产者和消费者的场景。由图进行分析:负责生产的线程T1不断制造新对象并插入到队列中,知道达到队列的上限值,此时,生产线程将被阻塞,知道消费者线程T2开始对这个队列进行消费。

同理,负责消费的线程不断从队列中消费对象,知道队列为空。当队列为空时,消费线程将会被阻塞,知道队列中有新的线程被插入进来。

2、BlockingQueue主要方法示意图:

3、BlockingQueue的主要实现类:

  • ArrayBlockingQueue
  • DelayQueue
  • LinkedBlockingQueue
  • PriorityBlockingQueue
  • SynchronousQueue

Java并发编程 (八) J.U.C组件拓展的更多相关文章

  1. Java并发编程 (七) J.U.C之AQS

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一. J.U.C之AQS-介绍 1.定义: AbstractQueuedSynchronizer简称AQ ...

  2. java并发编程(八) CAS & Unsafe & atomic

    参考文档:https://www.cnblogs.com/xrq730/p/4976007.html CAS(Compare and Swap) 一个CAS方法包含三个参数CAS(V,E,N).V表示 ...

  3. 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock

    ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...

  4. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  5. 【Java并发编程实战】-----“J.U.C”:CyclicBarrier

    在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...

  6. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  7. 【Java并发编程实战】-----“J.U.C”:Semaphore

    信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...

  8. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析

    前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...

  9. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介

    注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...

随机推荐

  1. 用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)

    用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1) 例子: a='12';b='1234567'; // 返回 0 a='47';b='1234 ...

  2. [hdu5521 Meeting]最短路

    题意:有N个点,给定M个集合,集合Si里面的点两两之间的距离都为Ti,集合里面的所有点数之和<=1e6.有两个人分别在1和N处,求1个点使得两个人到这一点距离的最大值最小 思路:这题是裸的最短路 ...

  3. Mysql 常用函数(1)- 常用函数汇总

    Mysql常用函数的汇总,可看下面系列文章 Mysql常用函数有哪几类 数值型函数 字符串型函数 日期时间函数 聚合函数 流程控制函数 数值型函数 函数名称 作用 ABS 求绝对值 SQRT 求二次方 ...

  4. vscode+eslint自动格式化vue代码的方法

    前言 使用vscode开发vue项目的时候,为了编码格式的统一化,使用eslint规范进行格式化.此时通过eslint插件可以实现对vue代码的自动格式化. 使用方式 在vscode的插件模块处,搜索 ...

  5. 2018-06-20 js字符串函数

    str.length -> 字符串长度; str.indexOf() -> 从左边查找字符串中某字符的位置: str.lastIndexOf -> 从右边查找字符串中某字符的位置: ...

  6. vue与其他框架对比

    https://cn.vuejs.org/v2/guide/comparison.html 1. vue 框架的特点? MVVM框架模式 轻量级,灵活,容易上手 数据驱动 组件化(单文件组件) 插件化 ...

  7. Java子父类间静态代码块、非静态代码块、构造方法的执行顺序

    子类A继承父类B,A a=new A(); 正确的执行顺序是:父类B静态代码块->子类A静态代码块->父类B非静态代码块->父类B构造函数->子类A非静态代码块->子类A ...

  8. 14.1 Go数据结构

    14.1 Go数据结构 每一个程序都在学习十八般武艺,学习语言.数据库.HTTP等技能. 而程序中的九阳神功就是数据结构与算,掌握了数据结构与算法,你的内功修炼就会有质的飞跃. 无论从事业务开发,测评 ...

  9. uwsgi模块以参数形式运行项目

    1.虚拟环境中下载uwsgi模块-------pip install uwsgi 2.脚本运行案例 新建一个test.py脚本文件,写入如下内容: def application(env, start ...

  10. 【Redis】List常见应用场景

    常用数据结构 Stack(栈) = LPUSH + LPOP ->FILO Queue(队列) = LPUSH + RPOP Blocking MQ(阻塞队列) = LPUSH + BRPOP ...