Juc并发
Juc(java.util.concurrent)工具包 原生的
1、概念
1.1、线程和进程
- 进程:进程是计算机中程序关于某数据集合的一次运行活动,是系统进行资源分配和调度的基本单位。
- 线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。/ 可以并发。
1.2、线程的状态
# New (新建状态)
# Runnable(准备状态)
# Blocked(阻塞状态)
# Waiting(持续等待) 回一直等待一个资源释放锁
# Timed_Waiting(定时等待) 过时回自动恢复运行状态
# Terminated(结束)
1.3、wati和sleep的区别
- sleep是thread的静态方法,wait是object的方法,任何对象实例都能调用
- sleep不会释放锁,他也不需要占用锁。wait回释放锁,但调用它的前提是当前线程占用锁(即代码要在synchronized中)
- 它们都可以被interupted方法中断。
1.4、并发和并行
- 并发:同一个时刻多个线程在访问同一个资源,多个线程对一个点
- 例子:春运抢票,电商秒杀。
- 并行:多项工作一起执行,最后在汇总
- 例子:可以同时对库存表和订单表进行操作。
1.5、管程(Monitor/监视器/锁)
-
他是一种同步机制,保证同一个时间,只有一个线程访问被保护的资源。
-
jvm的同步基于进入和退出,使用管程对象实现的。
- 执行方法前首先需要持有该方法的管程对象。
1.6、用户线程和守护线程
- 用户线程:用户自定义的线程
- 守护线程:比如垃圾回收
- 当程序当中的用户线程执行完毕之后就会自动退出程序。
2、Lock接口
2.1、可重入锁(ReentrantLock)
- 锁是可以重复使用,需要我们手动来控制上锁的解锁的过程
- 建议讲解锁放在final关键字代码块之中,防止出现异常导致资源不释放 。
package sync;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Wenigb
* @version V1.0
* @Package sync
* @date 2021/8/31 19:12
* @Copyright © 每天都是开心的一天呢
*/
//资源类
class Demo{
private int s = 10000;
// 获取锁对象
private final ReentrantLock reentrantLock = new ReentrantLock();
public void dels(){
// 加锁,注意加锁不能存放在try代码块里面,因为获取锁的时候有可能爆发异常,这样我们就会去走finally代码块释放锁,可是我们并没有拿到锁;
reentrantLock.lock();
try{
if (s>0){
System.out.println(Thread.currentThread().getName()+"卖出:"+(s--)+"剩余:"+s);
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 解锁,需要放在finally 代码块之中并且存放在第一行;
reentrantLock.unlock();
}
}
}
// 线程操作类
public class Stick implements Runnable {
private static Demo demo = new Demo();
public static void main(String[] args) {
new Thread(new Stick(),"aa").start();
new Thread(new Stick(),"bb").start();
new Thread(new Stick(),"cc").start();
}
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
for (int i = 0; i < 11000; i++) {
demo.dels();
}
}
}
2.2、Lock和synchronized 的区别
- Lock是接口,synchronized是关键字
- synchronized 在发生异常的时候会自动释放锁,不会导致死锁问题,而 Lock 在发生异常的时候,如果没有主动通过 **Unlock()**进行解锁,则很有可能造成死锁问题。
- Lock 可以让等待锁的线程响应中断,而 synchronized 却不行。使用synchronized 时,等待的线程会一直等待下去。
- 通过Lock可以知道有没有成功获取到锁,而 synchronized则不行。
- Lock 可以提高多个线程进行读操作的效率
- 在性能上,如果线程数量较少则两种的效率差不多,但是在线程数量过大的时候,Lock 接口的效率远远高于 Synchronized
3、线程之间的通信
两个线程同时对一个变量进行操作
- 资源类在进行操作资源的时候,首先判断一下该变量的状态是否允许操作 。
- 如果该变量的状态不允许操作的时候 使用 **wait()**方法来进行等待。
- 等其他线程的变量成功执行完成之后。使用 notifyAll() 方法来对线程进行唤醒
- 注:**wait()和notifyAll()**都是 Object 类自带的
虚假判断问题
- 如果我们的线程存放在if 语句中 就有可能出现虚假判断问题
# if判断
if(num == 0){
this.wait(); // 可能存在多个线程在这里等待,等唤醒的时候一次性全部唤醒导致对资源的误操作。
}
# while 判断
while(num == 0){
this.wait();
}
// if判断的话可能存在两个线程都在等待,而唤醒的时候导致两个线程一起被唤醒,执行错误操作;
定制化线程通信
- 我们可以通过定制标志位的方式来来进行定制化通信
# 定义一个标志位fig 通过切换标志位的方式来进行不同线程之间的定制化调用。
# 标志位不为1的时候等待
# 标志位不为2的时候等待
4、列表的线程安全问题
4.1、java并发修改异常
- https://www.jianshu.com/p/d1a384c58006 这里提供一条外链来解释兵法修改异常,这里主要说解决方案
4.2、解决方案
通过 Vector 列表接口来处理
List vector = new Vector(); // 通过这种方式来进行新建一个线程安全的列表。 java1.0提供,现在基本不用
通过 Collections 工具类来封装列表
List<Object> objects = Collections.synchronizedList(new ArrayList<>());
// 通过工具类可以将列表封装成线程安全的列表。 -- 比较古老,目前编程基本上很少用。
通过 JUC 提供的 CopyOnWriteArrayList 类来进行解决
List<Object> objects = new CopyOnWriteArrayList<>();
// 采用写时复制技术
// 线程进行读操作的时候,并发的进行读取
// 当线程需要进行写操作的时候,就复制一份原本的镜像用来进行写入。
// 等写完成之后于原本的内容进行合并。
扩展: HashSet 的线程不安全问题解决 (集合)
Set<Object> objects = new CopyOnWriteArraySet<>(); // 通过使用juc的线程安全集合类进行解决。
扩展:HashMap的线程不安全问题解决
Map<Object, Object> objectObjectConcurrentHashMap = new ConcurrentHashMap<>();
5、多线程锁
5.1、 synchronized 的锁是谁(锁的对象是)
- 对于普通同步方法,锁的是当前实例本身
- 对于静态同步方法,锁的的是类的Class对象(字节码对象)
- 对于同步方法块,锁的是 Synchronized括号里配置的对象。
5.2、公平锁和非公平锁
非公平锁
- 线程饿死 (一个线程将所有任务都完成)
- 效率高
公平锁
- 效率相对低
5.3、可重入锁
-
可重入锁是一个递归锁,及最外层拿到锁以后,方法内的其他方法也可以拿到锁。
-
public synchronized String add(){ this.add(); // 如果不是可重入锁的话,程序执行到这里会因为拿不到锁而导致死锁。因为锁已经被最外层的add方法拿到了。 } private static int number = 0; public static void main(String[] args) { new Test().add(); }
-
5.4、死锁
- 两个或两个以上的线程在争夺资源的时候导致的互相等待休眠的情况。
产生死锁的原因
- 系统资源不足
- 进程运行推进顺序不当
- 资源分配不当
验证是否是死锁
# 使用 jps 查看线程
// 使用jsp -l 命令来查看运行进程1`
// 使用jstack 进程编号 来查看堆栈信息
6、辅助类
6.2、减少计数 CountDownLatch
-
创建一个计数器,当计数器的初始值等于0的时候,我们会唤醒被该计数器所阻塞的线程
-
使用 计数器.await() 方法可以对线程进行阻塞
-
使用 计数器.countDown() 方法可以对技术器进行减一操作。
-
使用计数器.await() 方法可以对线程进行阻塞,直到计数器等于0的时候进行唤醒。
// 创建一个计数器,初始值是10 当计数器等于0的时候会唤醒 被 count.await()方法所阻塞的线程。
CountDownLatch count = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"号同学已经离开教室");
// 计数器减1
count.countDown();
},String.valueOf(i)).start();
}
// 计数器阻塞线程,直到计数器等于0的时候才会唤醒;
count.await();
System.out.println("班长锁门");
6.3、循环栅栏 CyclicBarrier
- 当等待的线程达到我们预定义的初始值以后才会执行我们传入的线程
- 只有当我们传入的线程执行完成以后,其他等人投票,待的线程才会继续执行。
// 线程操作类
public class Stick {
public static final Integer NUMBER = 7;
public static void main(String[] args) throws InterruptedException {
// 设置一个初始值,只有当等待的线程达到这个数值之后才会唤醒
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
System.out.println("召唤神龙");
});
for (int i = 0; i < 7; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"龙珠被收集到了");
try {
// 线程在这里会进行阻塞 只有等待 cyclicbarrier 里面的线程结束完成之后才会继续往下执行
cyclicBarrier.await();
// 么么哒只有当召唤神龙出现以后才会执行。因为线程在召唤神龙出现之前已经在等待状态了
System.out.println("么么哒");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
6.4、信号灯 Semaphore
- 可以创建一个线程池,设置最多存放数量
- 当数量到达上限以后,其他想要进入其中的线程会进行阻塞
- 在里面的线程可以使用 release()方法来退出线程池。
public static void main(String[] args) throws InterruptedException {
// 新建一个池,表示只能存放五条线程,存放满五条线程以后,其他线程如果想要进入线程会进行阻塞;
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
// 将当前线程存放进线程池
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"抢到了停车位");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 将当前线程从线程池种取出(空出一个位置)
semaphore.release();
}
},String.valueOf(i)).start();
}
}
7、读写锁
// 当写锁被占用的时候 读锁是不能进行读取的,只有当写锁释放以后才可以进行读取
// 写锁的优先级比较高,当有数据要写入的时候,等待一个线程读取完毕之后,下一个线程不会获取锁,而是等待写锁写完才进行读取
// 写锁写完假设有另外一个线程也在获取写锁,则读锁是不是给占用到的,会继续下一个写操作完毕之后在进行读取。
//资源类
class Demo{
private Map map = new HashMap();
// 新建一个读写锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key,Object value) throws InterruptedException {
// 获取写锁进行锁定 不可以并发
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"号线程正在写入中。");
TimeUnit.SECONDS.sleep(1);
map.put(key,value);
System.out.println("写入完成");
}finally {
// 释放锁
lock.writeLock().unlock();
}
}
public void get (String key){
// 获取读锁进行锁定 可以并发
lock.readLock().lock();
try {
System.out.println("读取成功,取出的值是:"+map.get(key));
}finally {
// 释放锁
lock.readLock().unlock();
}
}
}
// 线程操作类
public class Stick {
public static final Integer NUMBER = 7;
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
for (int i = 0; i < 5; i++) {
new Thread(()->{
String str = Thread.currentThread().getName();
try {
demo.put(str,str);
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
new Thread(()->{
String str = Thread.currentThread().getName();
demo.get(str);
},String.valueOf(i)).start();
}
}
}
// 输出结果
/**
*
0号线程正在写入中。
写入完成
1号线程正在写入中。
写入完成
2号线程正在写入中。
写入完成
4号线程正在写入中。
写入完成
3号线程正在写入中。
写入完成
读取成功,取出的值是:0
读取成功,取出的值是:1
读取成功,取出的值是:2
读取成功,取出的值是:3
读取成功,取出的值是:4
*
*/
- 读写锁缺点:
- 容易造成锁饥饿问题,一个线程一直再读,写线程永远无法写入(必须等待读线程读完才可以进行写入。)
读锁
-
共享锁,会发生死锁问题
-
死锁:
-
-
读锁ReadLock是支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问(写状态为0)时,读锁总是能够被成功地获取,而所做的也只是增加读状态(线程安全)。如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已经被获取,则进入等待状态。
-
# 第一条线程 与第二条线程在进行并发读操作,且有一个写操作,写操作必须等待线程2的读操作结束。 # 第二条线程 与第一条线程在进行并发读操作,且有一个写操作,写操作必须等待线程1的读操作结束。 # 两条线程互相等待,导致死锁问题。
-
# 第一条线程 与第二条线程在进行并发读操作,且有一个写操作,写操作必须等待线程2的读操作结束。 # 第二条线程 与第一条线程在进行并发读操作,且有一个写操作,写操作必须等待线程1的读操作结束。 # 两条线程互相等待,导致死锁问题。
-
写锁
-
独占锁,也会发生死锁问题
-
死锁
-
-
第一条线程在操作第一条记录的时候也操作了第二条记录,第二条线程在操作第二条记录的时候也操作了第一条记录,互相等待导致死锁。
-
写锁WriteLock是支持重进入的排他锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取读锁时,读锁已经被获取或者该线程不是已获取写锁的线程,则当前线程进入等待状态。读写锁确保写锁的操作对读锁可见。写锁释放每次减少写状态,当前写状态为0时表示写锁已背释放。
-
读写锁的演变过程
读写锁的缺点
-
饥饿锁:
-
一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。多线程中优先级高的会优先执行,并且抢占优先级低的资源,导致优先级低的线程无法得到执行。
我们知道多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿。当然还有一种饥饿的情况,一个线程一直占着一个资源不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够得到执行的,如那个占用资源的线程结束了并释放了资源。
与死锁不同的是,饥饿锁在一段时间内,优先级低的线程最终还是会执行的,比如高优先级的线程执行完之后释放了资源。
-
-
读的时候不能写,写的时候不能读
- 一个线程获取读锁以后不能获取写锁
- 因为你获取写锁需要等待读操作结束,但是我们并没有释放读锁,所以线程会等待读锁释放,由于线程阻塞无法释放读锁,会造成死锁。
- 一个线程获取写锁以后可以获取读锁。
- 一个线程获取读锁以后不能获取写锁
锁降级
- 目的: 锁降级可以保证数据在写入以后读取的时候数据不会发生 改变
- 假设线程1拿到了写锁写入了一条数据,另外一个线程2在进行操作数据之前,被其他线程3接着获取到了写锁,这种情况下可能导致线程2操作的数据并不是线程1进行写入的。
/* 代码中声明了一个volatile类型的cacheValid变量,保证其可见性。首先获取读锁,如果cache不可用,则释放读锁,获取写锁,在更改数据之前,再检查一次cacheValid的值,然后修改数据,将cacheValid置为true,然后在释放写锁前获取读锁;此时,cache中数据可用,处理cache中数据,最后释放读锁。这个过程就是一个完整的锁降级的过程,目的是保证数据可见性,如果当前的线程C在修改完cache中的数据后,没有获取读锁而是直接释放了写锁,那么假设此时另一个线程T获取了写锁并修改了数据,那么C线程无法感知到数据已被修改,则数据出现错误。如果遵循锁降级的步骤,线程C在释放写锁之前获取读锁,那么线程T在获取写锁时将被阻塞,直到线程C完成数据处理过程,释放读锁。 */
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
8、阻塞队列
8.1、概述
- 当队列是空的时候,从队列之中获取元素将会被阻塞
- 当队列是满的时候,从队列之中添加元素将会被阻塞
8.2、分类
-
- 可以提供一个定长的阻塞队列
-
- 定长的阻塞队列。由链表数据结构组成
8.3、常用方法
9、线程池 (thread pool)
9.1、线程池的优势
- 线程池做的工作只要是运行的线程数量,在处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等待,等其他线程执行完毕,再从队列中取出任务来执行。
特点
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行
- 提高线程的可管理性: 线程时稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以统一的分配,调优和监控;
- java中线程池时通过 Excutor 框架来实现的,该框架中用到了 Executor Executors ExecutorService ThreadPoolExecutor 这几个类
9.2、线程的使用方式
-
一池N线程
public static void main(String[] args) throws InterruptedException { ExecutorService ex = Executors.newFixedThreadPool(10); try { for (int i = 0; i < 20; i++) { ex.execute(()->{ System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } }catch (Exception e){ e.printStackTrace(); }finally { ex.shutdown(); } }
-
一池一线程
public static final Integer NUMBER = 7; public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService ex = Executors.newSingleThreadExecutor(); try { for (int i = 0; i < 20; i++) { ex.execute(()->{ System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } }catch (Exception e){ e.printStackTrace(); }finally { ex.shutdown(); } }
-
一池可扩容
public static final Integer NUMBER = 7; public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService ex = Executors.newCachedThreadPool(); try { for (int i = 0; i < 20; i++) { ex.execute(()->{ System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } }catch (Exception e){ e.printStackTrace(); }finally { ex.shutdown(); } }
底层原理
-
三种创建线程都是 new 一个 ThreadPoolExecutor 对象
-
ThreadPoolExeutor 的七个参数(构造参数)
# int corePoolSize // 常驻线程数量(核心) # int maximumPoolSize // 最大线程数量 # long keepAliveTime # TimeUnit unit // 这两个是线程存活时间的存货单位 // 只在高峰期线程池会临时创建线程,临时创建线程的存活时间,而不是核心线程的存活时间。 // 临时线程数量不超过最大线程数量。 # BlockingQueueM<Runnable> workQueue // 阻塞队列 # ThreadFactory threadFactory // 线程工厂 # RejectedExecutionHandler handler // 拒绝策略
9.3、线程池的执行流程
- 当任务数量到达线程池核心线程数量以后,新的线程进来会放到工作队列之中。
- 如果工作队列满了以后,就会创建新的线程来进行处理(临时线程)
- 如果线程数量达到最大值以后,新建线程将会被拒绝,可以通过配置拒绝策略来指定。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果当前正在运行的线程数小于corePoolSize,则创建新的线程
//执行当前任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果当前运行的线程数大于等于corePoolSize或者线程创建失败
//则把当前任务放入工作队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//判断之前是否已经添加过线程执行该任务(因为可能之前)
//创建的线程已经死亡了)或者线程池是否已经关闭。如果
//两个答案都是肯定的,那么选择拒绝执行任务
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果线程池任务无法加入到工作队列(说明工作队列满了)
//创建一个线程执行任务。如果新创建后当前运行的线程数大于
//maximumPoolSize则拒绝执行任务
else if (!addWorker(command, false))
reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core){
//省略部分代码
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//这里就将提交的任务封装成为Worker了
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//使用加锁的方式原子添加工作线程
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//在获得锁期间再次检查线程池的运行状态:如果
//线程池已经关闭或者任务为空则抛出异常
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
//加入Worker数组
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//如果添加成功则启动线程执行任务
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
拒绝策略配置
9.4、自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
// 线程池核心线程
2,
// 线程池最大线程
5,
// 临时线程存活时间
2,
// 时间单位
TimeUnit.SECONDS,
// 工作队列 (阻塞队列)
new ArrayBlockingQueue<>(2),
// 默认的线程池创建工厂
Executors.defaultThreadFactory(),
// 拒绝策略
new ThreadPoolExecutor.AbortPolicy()
);
10、分支合并框架 fork/join
//资源类
// 分支合并核心类 RecursiveTask<Integer>
class Demo extends RecursiveTask<Integer>{
private static final Integer VALUE = 10;
private Integer start;
private Integer end;
private Integer result=0;
// 通过构造函数传入我们需要的资源
public Demo(int start,int end){
this.start = start;
this.end = end;
}
/**
* The main computation performed by this task.
* 这里是拆分逻辑,我们在这里将我们的业务进行递归拆分
* 可以理解为这个方法将会被递归执行
* @return the result of the computation
*/
@Override
protected Integer compute() {
// 判断两个值是否大于Value
// 这里是递归链条
if ((end-start)<=VALUE){
for (int i = start; i <= end ; i++) {
result += i;
}
}else{
int middle=(end+start)/2;
Demo demo = new Demo(start, middle);
Demo demo1 = new Demo(middle + 1, end);
// 对任务进行拆分,可以简单理解为新建一条线程(实际上并不是,只是新建一条任务)
demo.fork();
demo1.fork();
// 合并多个线程的计算结果
result+=demo.join()+demo1.join();
}
// 最终返回执行结果
return result;
}
}
// 线程操作类
public class Stick {
public static final Integer NUMBER = 7;
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 新建执行任务
Demo demo = new Demo(0, 100);
// 新建分支合并线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 提交分支合并任务
ForkJoinTask<Integer> submit = forkJoinPool.submit(demo);
// 获取分支合并结果
System.out.println(submit.get());
}
}
11、异步回调
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 同步调用 使用runAsync方法
CompletableFuture<Void> hello = CompletableFuture.runAsync(() -> {
System.out.println("hello");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 线程在这个地方会进行阻塞 等待调用线程执行完成才追结束
hello.get();
System.out.println("同步调用结束");
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "么么";
});
// 这里value是计算结果 ex是异常信息 如果没有运行结果(中间爆发异常)则为null,如果没有异常则ex为null
// 这里线程在执行get方法的时候也会进行响应
// 异步调用和同步调用的区别:异步调用可以指定对数据的处理方法
async.whenComplete((value,ex)->{
System.out.println(value);
}).get();
System.out.println("异步调用结束");
}