什么是JUC?
JUC指的是
java.util.concurrent
包下的所有提供的工具类的简称。这是一个处理线程的工具包,JDK1.5开始出现的。
什么是进程?
进程(Process)
:指的是计算机中的程序关于某数据集合上的一次运行活动。是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及组织形式的描述,进程是程序的实体。
什么是线程?
线程(Thread)
:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的时机运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1、线程是独立调度和分派的基本单位。
2、同一进程中的多条线程将共享该进程中的全部系统资源。
3、一个进程可以有很多线程,每条线程并行执行不同的任务。可并发执行。
二者的区别?
进程与线程其实并没有过多的关系。
- 进程基本上是相互独立的存在, 而线程则存在于进程中,是进程的一个子集。
- 同时进程中拥有共享的资源,如:内存空间等,供进程内线程共享
- 线程通信则比进程简单,因为共享内存空间,所以一个进程中多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换成本要比进程切换低
并行与并发
单核 cpu 下,线程实际还是
串行执行
的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。总结为一句话就是:
微观串行,宏观并行
,一般会将这种
线程轮流使用 CPU
的做法称为并发(concurrent)
其实就是CPU时间片在某段时间内分配的策略问题
串行模式:
串行模式
:即表示所有任务都是按先后顺序进行。串行是一次只能取的一个任务,并执行这个任务。
举个生活中的小例子:就是在火车站买票,今天只开放这一个窗口卖票,那么我们只有等到前面的人都买了,才能轮到我们去买。即按先后顺序买到票。
并行模式
:
并行模式
概述:一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。对比地,并发是指:在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。
并行模式
:并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。
我们还是用上面那个例子:还是在买票,以前是只有一个窗口卖票,但是近几年发展起来了,现在有五个窗口卖票啦,大大缩短了人们买票的时间。
并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。不过并行的效率,一方面受多进程/线程编码的好坏的影响,另一方面也受硬件角度上的CPU的影响。
并发
:
并发
并发:并发指的是多个程序可以同时运行的一种现象,并发的重点在于它是一种现象,并发描述的是多进程同时运行的现象。但真正意义上,一个单核心CPU任一时刻都只能运行一个线程。所以此处的”同时运行”表示的不是真的同一时刻有多个线程运行的现象(这是并行的概念),而是提供了一种功能让用户看来多个程序同时运行起来了,但实际上这些程序中的进程不是一直霸占 CPU 的,而是根据CPU的调度,执行一会儿停一会儿。
- 并发(concurrent)是同一时间应对(dealing with)多件事情的能力
- 并行(parallel)是同一时间动手做(doing)多件事情的能力
创建线程的几种方式
实现Runnable接口
class TestRunnable {
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("实现了Runnable接口");
}
}
继承Thread类
public class TestExtendThread {
public static void main(String[] args) {
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("继承了Thread");
}
}
实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class TestCallable {
public static void main(String[] args) {
FutureTask<Void> futureTask = new FutureTask<>(new MyThreadCallable());
new Thread(futureTask).start();
}
}
class MyThreadCallable implements Callable<Void> {
@Override
public Void call() throws Exception {
System.out.println("实现Callable接口");
return null;
}
}
还有一种根据线程池创建线程,后面会有解释。
线程状态
public enum State {
NEW,
RUNNABLE(运行),
BLOCKED (阻塞),
WAITING (等待),
TIMED_WAITING (超时等待时间,相比于WAITING,它可以指定时间自行返回),
TERMINATED (结束);
}
wait / sleep方法之前的区别
-
wait
是
Object
类中的方法,所有类都可以调用。
sleep
方法是
Thread
类中的方法。 -
sleep
不会释放锁,它也不需要占用锁。
wait
方法会释放锁,但调用它的前提是当前线程占有锁(即代码要在
synchronized
中)。 -
它们都可以被
interrupted
方法中断。
(Monitor)
Java中被称为锁,操作系统中被称为监视器。一种同步机制,保证同一个时间只有一个线程访问被保护的数据或代码。 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 Monitor。
JVM同步基于进入和退出,使用管程对象实现的(加锁和解锁)
用户线程和守护线程
用户线程
:自定义线程(除非专门setDaemon否则用户设置的线程都是用户线程),main方法是用户线程(也就是主线程是用户线程)
守护线程
:当程序结束时,线程才会结束(GC线程)
如果程序中不存在任何用户线程后,守护线程会自动结束。
Lock接口
什么是 Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。
Lock 与的 Synchronized 区别
Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问 ;“Lock和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而
Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象
。
Lock和 synchronized 有以下几点不同 :
-
Lock 是一个接口,而 synchronized 是Java 中的关键字,synchronized 是内置的语言实现
-
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock0去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
-
Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断:
-
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
-
Lock 可以提高多个线程进行读操作的效率。
Synchronized 相比于Lock更加重量级,同时Lock相比Synchronized要更加灵活。
synchronized实现同步的基础:
Java中的每一个对象都可以作为锁具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象
对于静态同步方法,锁是当前类的class对象。
对于同步方法块,锁是synchonized括号里配置的对象
可重入锁
什么是可重入?
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。例如
Synchronized
package com.llq.jucdemo.juc;
public class Test01 {
public static void main(String[] args) {
Object o = new Object();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "外部");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "中部");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "内部");
}
}
}
}, "测试线程").start();
}
}
Lock
package com.llq.jucdemo.juc;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test01 {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(() -> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "外部");
try {
System.out.println(Thread.currentThread().getName() + "内部");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "测试Lock线程").start();
}
}
使用ReentrantLock的注意点
ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样。
错误实例:
什么是死锁?
死锁是一组相互竞争的两个或两个以上的线程进行资源竞争而互相等待导致“永久“阻塞的现象
产生死锁的几个条件:
1、系统资源不足
2、进程推进顺序不合适
3、资源分配不当
package com.llq.jucdemo.juc;
public class TestDeadLock {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (a) {
System.out.println("线程1获取资源a,试图获取资源b");
synchronized (b) {
System.out.println("线程1获取到资源b");
}
}
}, "线程2").start();
new Thread(() -> {
synchronized (b) {
System.out.println("线程2获取资源b,试图获取资源a");
synchronized (a) {
System.out.println("线程2获取到资源a");
}
}
}, "线程2").start();
}
}
Callable接口详解
Callable: 返回结果并且可能抛出异常的任务。
优点:
可以获得任务执行返回值;
通过与Future的结合,可以实现利用Future来跟踪异步计算的结果。
Runnable和Callable的区别:
- Callable规定的方法是call(),Runnable规定的方法是run().
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
- call方法可以抛出异常,run方法不可以
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
- 代码示例:
//Callable 接口
public interface Callable<V> {
V call() throws Exception;
}
// Runnable 接口
public interface Runnable {
public abstract void run();
}
使用示例:
package com.llq.jucdemo.juc;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() -> {
int i = 0;
for (int j = 0; j < 100; j++) {
i+=j;
}
return i;
});
new Thread(task).start();
while(!task.isDone()) {
System.out.println("waiting complete");
}
System.out.println(task.get());
System.out.println(task.get());
}
}
FutureTask任务最后计算结果只会汇总一次,第二次执行get()方法时会直接返回。
CountDownLatch
CountDownLatch 类可以设置一个计数器,然后通过 countDowr[方法来进行减 1的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句。
- CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻寨
- 其它线程调用 countDown方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
- 当计数器的值变为 0 时,因 await方法阻塞的线程会被唤醒,继续执行
package com.llq.jucdemo.juc;
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(6);
new Thread(() -> {
try {
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "----我执行了!!!!!------" + countDownLatch.getCount());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "执行Await线程").start();
new Thread(() -> {
for (int j = 0; j < 6; j++) {
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " ---- " + countDownLatch.getCount());
}
}, "执行countDown线程1").start();
new Thread(() -> {
for (int j = 0; j < 6; j++) {
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " ---- " + countDownLatch.getCount());
}
}, "执行countDown线程2").start();
}
}
CyclicBarrier
CyclicBarrier是一个同步辅助类,它允许一组线程与相等待,直到到达某个公共展点(common baier point。在涉及一固定大小的线理的程序中,这些线程必须不时地互相等待,此时CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。 cyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后( 但在释放所有线程之前,该命令只在每屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。
package com.llq.jucdemo.juc;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class TestCyclicBarrier {
public static void main(String[] args) {
int num = 5;
CyclicBarrier barrier = new CyclicBarrier(num, () -> System.out.println("当前已经满足条件,我执行了!!!"));
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
System.out.println("线程" + Thread.currentThread().getName() + "阻塞了!");
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}, String.valueOf(i)).start();
}
}
}
如果前置条件没有满足,当前进程则不会停止,会继续阻塞,当满足最终的条件后,程序结束。
Semaphore(信号量)
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可每个release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Seaphore 只对可用许可的号码进行计数,并采取相应的行动。
package com.llq.jucdemo.juc;
import java.util.concurrent.Semaphore;
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("线程" + Thread.currentThread().getName() + "获得了信号量");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
读写锁
1、什么悲观锁?
顾名思义,悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的;
特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;
2、什么是乐观锁?
乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);
特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。
数据库表锁和行锁
表锁
当我对数据库表第一条数据修改时,对数据库表进行加锁,其他修改类型的操作必须等前置操作处理完,释放锁后才能执行。
行锁
读写锁的使用
package com.llq.jucdemo.juc;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private static volatile HashMap map = new HashMap();
private static final ReadWriteLock lock = new ReentrantReadWriteLock();
private static void putData(Object key, Object value) {
try {
lock.writeLock().lock();
map.put(key, value);
TimeUnit.MICROSECONDS.sleep(1000);
System.out.println("当前线程" + Thread.currentThread().getName() + "---存入完成");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
}
}
private static void getData(Object key) {
try {
lock.readLock().lock();
map.get(key);
TimeUnit.MICROSECONDS.sleep(1000);
System.out.println("当前线程" + Thread.currentThread().getName() + "---取出完成");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.readLock().unlock();
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
putData(num + "", num + "");
}, String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
getData(num + "");
}, String.valueOf(i)).start();
}
}
}
一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享的。
注意:读写锁缺点:(1)读操作时可以并发读,但是如果一直不进行写操作,会造成锁饥饿。(2)读操作时,是不能进行写操作的,必须等读操作执行完成后才能进行写操作,但是写操作时是可以进行读操作的。
锁降级
读锁不能升级为写锁
package com.llq.jucdemo.juc;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LowLevelReadWriteLock {
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
writeLock.lock();
System.out.println("写入操作");
readLock.lock();
System.out.println("读出操作");
writeLock.unlock();
readLock.unlock();
}
}
阻塞队列
什么是阻塞队列?
队列(FIFO【First in First out】,先进先出的特点)
BlockingQueue 阻塞队列,排队拥堵,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如下图所示:
队列和栈不同,栈是先进后出(LIFO【Last in First Out】)。
- 当队列是空的,从队列中获取元素的操作将会被阻塞
- 当队列是满的,从队列中添加元素的操作将会被阻塞.
- 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素.
- 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
线程池的使用
什么是线程池?
线程池(Thread pool)
: 一种线程使用模式。线程过会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
线程池的优势
: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行
主要特点
:
降低资源消耗
: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗
提高响应速度
: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
提高线程的可管理性
: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,ExecutorsExecutorService,ThreadPoolExecutor这几个类
线程池的几种使用方式
**newFixedThreadPool(int):**无上限线程池
**newSingleThreadExecutor():**单条线程线程池
**newCachedThreadPool():**根据需求创建线程,可以扩容
**newScheduledThreadPool:**支持定时或周期任务的线程池(核心线程数=自定义,最大线程数=Integer.MAX_VALUE)
示例代码:
package com.llq.jucdemo.juc;
import java.util.concurrent.*;
public class TestExecutor {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newCachedThreadPool();
// try {
// for (int i = 0; i < 20; i++) {
// threadPool.execute(() -> {
// System.out.println(Thread.currentThread().getName());
// });
// }
// } finally {
// threadPool.shutdown();
// }
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 8,
1200, TimeUnit.SECONDS, new ArrayBlockingQueue<>(8), new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 20; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
} finally {
threadPool.shutdown();
}
}
}
注意:开发时不允许使用Executors创建线程池
线程池之拒绝策略
当线程池的线程数达到最大线程数时,同时阻塞队列也满了之后,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:
AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
Fork/Join框架
Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
我们再通过 Fork 和 Join 这两个单词来理解下 Fork/Join 框架,Fork 就是把一个大任务切分为若干子任务并行的执行,Join 就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算 1+2+。。+10000,可以分割成 10 个子任务,每个子任务分别对 1000 个数进行求和,最终汇总这 10 个子任务的结果。Fork/Join 的运行流程图如下:
package com.llq.jucdemo.juc;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask myTask = new MyTask(0, 100);
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> task = forkJoinPool.submit(myTask);
Integer result = task.get();
System.out.println(result);
forkJoinPool.shutdown();
}
}
class MyTask extends RecursiveTask<Integer> {
private static final Integer VALUE = 10;
private Integer beginNum;
private Integer endNum;
private Integer resultNum = 0;
public MyTask(Integer beginNum, Integer endNum) {
this.beginNum = beginNum;
this.endNum = endNum;
}
@Override
protected Integer compute() {
if ((endNum - beginNum) <= VALUE) {
for (int i = beginNum; i <= endNum; i++) {
resultNum += i;
}
} else {
Integer middleNum = (beginNum + endNum) / 2;
MyTask myTask1 = new MyTask(beginNum, middleNum);
MyTask myTask2 = new MyTask(middleNum + 1, endNum);
myTask1.fork();
myTask2.fork();
resultNum = myTask1.join() + myTask2.join();
}
return resultNum;
}
}
CompletableFuture的使用
package com.llq.jucdemo.juc;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(() -> {
System.out.println("completableFuture1 execute!!!!!");
});
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
if (100 > 1) {
throw new RuntimeException("抛出异常!!!!");
}
return 10000;
});
completableFuture1.get();
completableFuture2.whenComplete((t, u) -> {
System.out.println(t);
System.out.println(u);
}).get();
}
}
异步调用中可以执行一些远程调用请求,当多个远程调用需要同时进行时,就可以使用CompletableFuture函数执行对应的异步请求来加快接口的响应速度。