JUC并发编程(入门)

一、JUC概述

1. 什么是JUC

JUC是Java.util.concurrent包,在jdk1.5版本首次出现。

2.进程和线程

2.1.进程和线程的区别

  • 进程:系统进行资源分配和调度的基本单位。当程序运行时,就是一个进程。(软件实际占用多少空间)
  • 线程:系统能够进行运算调度的最小单元。进程的实际运作单位,程序执行的最小单位。一个进程可能会开启多个线程,如在qq中打开资料修改,聊天,这些功能都会开启一个或者多个线程。

2.2.线程的状态

  • 线程的状态分五种。
  • 分别为:NEW(新建),RUNNABLE(就绪),BLOCKED(阻塞),WAITING(等待),TIME_WAITING(定时等待),TERMINATED(终结)。
  • 其中WAITING(等待),TIME_WAITING(定时等待)都为等待状态。

2.3.wait和sleep的异同

  • 相同点:都能被interrupted打断。
  • 不同点
来源占锁
waitObject方法会释放锁,前提是当前线程占用锁
sleepThread静态方法不会释放锁,也不需要占用锁

2.4.并行和并发和串行

串行:多个操作单个执行。
并行:多个操作同时执行。
并发:同一个时刻,多个线程访问同一个资源。

2.5.锁

  • 管程、Monitor、监视器、锁都代表锁。
  • 监视器是一种同步机制,保证同一个时间,只有线程访问被监视器的代码或数据。
  • JVM同步基于进入与退出,对管程对象的持有与释放来实现。当一个线程持有管程对象,其他线程不能获取持有这个管程对象。

2.6.用户线程与守护线程

用户线程:自定义线程。
守护线程:系统、后台线程。如gc。
代码测试

class test{
	public static void main(String[] args){
		Thread aa = new Thread(()->{
			System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().isDaemon);
			while(true){

            }
		},"AA");
		//aa.setDaemon(true);//设置守护线程,默认为false
		aa.start();//
		System.out.println(Thread.currentThread().getName()+"Over");
		//输出结果1:main线程已经结束,程序依然运行,输出AA:flase。jvm仍然存活
		//输出结果2:设置守护线程后,主线程结束,守护线程也结束。jvm结束。
	}
}

运行过程问题:设置守护线程后仍然输出了AA,怀疑是系统调度的问题,使用sleep后解决。

二、Lock接口

1. synchronized关键字复习

synchronized:java关键字,同步锁。
修饰范围:代码块、方法、静态方法、类。
作用范围:锁住某个括号范围,锁住某个对象。

2.多线程固定编程步骤(上)

2.1.常见线程创建方法

主要是实现run()方法。(记住线程创建后调用start()方法启动)
Thread类继承:

//lamda表达式
new Thread(()->{});

Runnable接口:函数式接口,只有一个run()方法

new Thread(new Runnable(){
	public void run(){
		...
	}
});

2.2.步骤

  1. 创建资源类,实现属性和操作方法。
  2. 创建多线程,调用资源类的方法。
卖票实例(sync实现)
//1.建立资源,实现属性和操作方法
class Tickets {
    //票数
    private int number = 30;
    //操作方法(添加锁的方式)
    public synchronized void sale() {
        //判断是否有票
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + ":买票成功" + number-- + "剩下:" + number);
            try {
                Thread.sleep(5);//模拟延时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//第二步:创建多个线程,调用资源类的操作方法
public class SaleTicket {
    public static void main(String[] args) {
        //创建Ticket对象
        Tickets ticket = new Tickets();
        //创建多线程来进行卖票
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 30; i++) {
                    //调用卖票方式
                    ticket.sale();
                }
            }
        },"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 30; i++) {
                    ticket.sale();
                }
            }
        },"BB").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 30; i++) {
                    ticket.sale();
                }
            }
        },"CC").start();
        
    }
}

2.3.lock接口

lock接口有三个实现类。ReentrantLock:可重入锁ReentrantReadWriteLock.ReadLock:读锁,ReentrantReadWriteLock.WriteLock:写锁

如何使用
例:可重入锁

class xx{
	private final ReentrantLock lock = new ReentrantLock();//创建锁对象
	public void m(){
		lock.lock();//上锁
		try{
			...//方法体
		}finally{
			lock.unlock();//解锁
		}
	}
}

可重入锁特点:可以多次,被上锁,解锁。(持有锁时不能被其他线程上锁)

卖票实例(lock接口实现)
class Tickets{
	private int num = 30;
	//创建锁对象
	 
	//操作方法
	public void sale(){
		//上锁
		lock.lock();
		try{
			if (number > 0) {
            System.out.println(Thread.currentThread().getName() + ":买票成功" + number-- + "剩下:" + number);
            Thread.sleep();
		}catch(InterruptedException e){
			e.printStackTrace();	
		}finally{
			lock.unlock();//手动解锁
		}
	}
}
//2.创建多线程方法与上相同

2.4.lock接口与synchronized关键字的区别

  1. lock接口不是java语言内置,synchronized是java关键字
  2. lock是一个类,通过实现类实现同步访问。
  3. synchronized:当方法或代码块执行完毕后自动释放锁,而lock需要手动解锁。当遇到异常时,synchronized会自动解锁,而lock需要手动解锁,所以需要加到finally中。
  4. lock可以让等待锁的线程响应中断,而使用synchronized则需要一直等待下去,不能响应中断。
  5. 通过lock可以知道有没有成功获取到锁,synchronized不行
  6. lock可以提高多个线程进行读操作的效率
    PS:调用start()方法时,是否创建线程由系统决定。

三、线程间的通信

1.多线程编程步骤

  1. 创建资源类(属性和操作方法)
  2. 操作方法:判断(根据条件判断是否进行操作)、干活(实际操作)、通知(唤醒)
  3. 在操作方法的判断中使用:while() 。原因:防止虚假唤醒
  4. 创建多个线程,调用资源类的操作方法

1.1.实例

实现两个线程对num修改,输出 0 1 0 1 0 1 …

//第一步创建资源类,定义属性与操作方法
class Share {
    //属性
    private int num = 0;

    //操作方法:+1
    public synchronized void incr() {
        //第二步 判断 干活 通知
        while (num != 0) {//判断是否等于0,等于0 就+1,不是0等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果number是0,就+1操作
        num++;
        System.out.println(Thread.currentThread().getName() + "::" + num);
        //通知其他线程
        this.notifyAll();
    }

    public synchronized void decr() {
        //第二步 判断 干活 通知
        while (num != 1) {//判断是否等于1,等于1进行-1 操作,不等于1等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果number是1,进行-1 操作
        num--;
        System.out.println(Thread.currentThread().getName() + "::" + num);
        //通知其他线程
        this.notifyAll();
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        //第三步:创建多个线程,调用资源类的操作方法
        Share share = new Share();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                share.incr();//+1操作
            }
        }, "aa").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                share.decr();//-1操作
            }
        }, "bb").start();
    }
}

1.2.虚假唤醒问题

产生条件(原因):wait() 方法在哪儿睡,就在哪儿醒。所以,当线程被唤醒时,上一次已经执行了if()语句的判断,就跳出了这次的判断,直接执行+1/-1操作。

1.3.Condition的初次使用

Condition依赖与lock,可以看作为一个变量

1.3.1.创建Condition实例和方法

Condition condition = lock.newCondition();
condition.await();//被中断或接到信号前一直等待
condition.signal();//唤醒当前线程
condition.signalAll();//唤醒所有线程

实例1:使用Condition实现abcd四个线程的输出0 1 0 1操作

...

实例2:使用Condition实现:输出aa5次,输出bb10次,输出cc15次,共10轮。

实现方案:设置标志位flag

  • flag=1 : aa
  • flag=2 : bb
  • flag=3 : cc
    创建三个condition,使用signal()与wait()方法实现线程间的定制化通信。

//第一步 创建资源类
class ShareResource{
    //定义标志位
    private int flag = 1;//aa:1 bb:2 cc:3
    //创建lock锁
    private final Lock lock = new ReentrantLock();

    //创建三个condition(三个线程)
    private final Condition c1 = lock.newCondition();
    private final Condition c2 = lock.newCondition();
    private final Condition c3 = lock.newCondition();

    //打印5次,参数第几轮 aa
    public void print5(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            //判断
            while(flag!=1){
                c1.await();
            }
            //干活
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);
            }
            //通知
            flag = 2;//修改标志位
            c2.signal();//通知bb线程
        }finally{
            //解锁
            lock.unlock();
        }
    }

    //打印10次,参数第几轮 bb
    public void print10(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            //判断
            while(flag!=2){
                c2.await();
            }
            //干活
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);
            }
            //通知
            flag = 3;//修改标志位
            c3.signal();//通知cc线程
        }finally{
            //解锁
            lock.unlock();
        }
    }

    //打印15次,参数第几轮 cc
    public void print15(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            //判断
            while(flag!=3){
                c3.await();
            }
            //干活
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);
            }
            //通知
            flag = 1;//修改标志位
            c1.signal();//通知aa线程
        }finally{
            //解锁
            lock.unlock();
        }
    }

}

public class ThreadDemo3 {
    public static void main(String[] args) {
        //创建资源实例
        ShareResource resource = new ShareResource();
        //创建多线程总共10轮
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    resource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"aa").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    resource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"bb").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    resource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"cc").start();
    }
}

五、集合的线程安全

1.List不安全的示例

如ArrayList

class test{
	public static void main(String[] args){
		List<String> list = new ArrayList<>();
		//创建多个线程
		for(i=0;i<10;i++){
			new Thread(()->{
				//添加
				list.add(UUID.randomUUID().toString().subString(0,8));
				//取出
				System.out.println(list);
			},String.valueOf(i)).start();
		}
	}
}
//异常:ConcurrentModifyCationException 并发修改异常

2.List线程不安全解决方案

三种方案,Vector,Collections,CopyOnWriteArrayList类

2.1.Vector

  • Vector是jdk1.0提出的古老方案,一般不用。
  • 底层所有的方法都添加了Synchronized关键字,能保证并发修改不会出现异常,但是性能差,效率低。
  • 创建方法:
List<String> list = new Vector<>();

2.2.Collections

  • Collections工具w类提供同步方法,创建同步的list。效率低,也不用。
  • 创建方法:
List<String> list = Collections.synchronized(new ArrayList<>());

2.3.CopyOnWriteArrayList

  • 写时复制技术(支持并发读,独立写)
  • add()方法:底层使用重入锁实现
  • 当添加时:复制一个比原集合长度多1的集合,把新元素添加进去,最后把新集合当做要用的集合。
  • private transient volatile Object[] array;
  • 补: Volatile的可见性:被volatile修饰的成员变量被线程修改时,都强迫从主内存中重读该成员变量的值。而且,当成员变量发生改变时,强迫将变化值写回到主内存,这样在任何时刻,两个线程看到的为同一个值。
  • 详情见
    Volatile和Transient
    JAVA并发编程: CAS和AQS
    -创建方法:
List<String> list = new CopyOnWriteArrayList<>();

3.HashSet线程不安全解决方案

创建:

Set<String> set = new HashSet<>();
//同样修改出现了并发修改异常

解决方案:JUC提供的CopyOnWriteArraySet方法

4.HashMap不安全解决方案

JUC提供的ConcurrentHashMap<K,V>
底层:采用数组+链表+红黑树
保证线程安全:synchronized+CAS操作(不支持key为null或value为null)

六、锁

1.8种情况的锁

…省略
结果:讨论对锁的范围问题。

  • 对于普通方法:当前实例
  • 静态同步方法:当前的class
  • 同步方法块:sync括号里配置的对象

2.公平锁和非公平锁

表现:在买票实例中,不对A进行sleep,A卖光了所有票,造成BC被饿死的情况。

使用:

final ReentrantLock lock = new ReentrantLock();
//true:公平锁,效率较低
//false:非公平锁(默认无参为非公平锁),线程效率高,但造成线程饿死

synchronized为非公平锁

3.可重入锁(递归锁)

synchronized(隐式)和lock(显式)都为可重入锁。
广义:可重复,可递归调用的锁。在外层使用锁之后,在内层仍然可以获取到锁,并且不发生死锁。
定义:支持一个线程对资源的重复加锁。
实现:

  1. 线程再次获取到锁:锁需要识别获取线程的锁是否为当前占据锁的线程。(判断)
  2. 锁的最终释放:线程重复n次获取锁,在n次释放锁后,其他线程能够获取。(设置计数器:获取锁计数自增,释放锁计数自减,为0表示成功释放。每次释放都要判断状态值是否为0,前(n-1)次释放应该都为0)

4.死锁

定义:两个或以上的线程在执行中,因为争夺资源而造成一种相互等待的现象,没有外力干涉将无法继续进行下去。
产生条件互斥、请求保持、不可剥夺、循环等待
产生原因:系统资源不足、进程推进顺序不合适、资源分配不当
验证死锁:
(1)jps指令 类似 linux:ps -ef
(2)jstack jvm自带栈跟踪的工具

public class DeadLock{
	static Object a = new Object();
	static Object b = new Object();
	public static void main(String[] args){
		new Thread(()->{
			synchronized(a){
				sout("aaa");
				sout("want bbb");
				synchronized(b){
					sout("bbb");
				}
			}
		}).start();
		
		new Thread(()->{
			synchronized(b){
				sout("bbb");
				sout("want aaa");
				synchronized(a){
					sout("aaa");
				}
			}
		}).start();
	}
}

指令使用方式

七、Callable接口

  • 创建多线程的方式:
  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池

1.Runnable与Callable的区别

接口方法:
Callable:V call();返回计算结果,无法抛出异常
Runnable:void run(); 无返回结果

2.使用Callable接口

  1. Runnable实现类下有:FutureTask
  • FutureTask(Callable<> callable)
  • FutureTask(Runnable, result)
  1. FutureTask方法
  • V get():获取结果
  • boolean isDone:如果完成,返回ture

2.1.FutureTask(未来任务)概述,原理

为某个任务单开启线程,先计算其他。最后汇总结果。
如:
a:1+2+3…+10 b:11+12+…+50 c:50+51+…+60
我们为b单开启一个线程,当需要的时候我们直接使用get获取结果就行。不需要等b重新进行计算。
实例:使用FutureTask<>创建线程

/**
 * 比较runnable 与 Callable 接口
 */
class Thread1 implements Runnable{
    @Override
    public void run() {
    }
}

class Thread2 implements Callable{
    @Override
    public Integer call() throws Exception {
        return 200;
    }
}

public class Demo1 {
    public static void main(String[] args) {
//        Thread1 thread1 = new Thread1();
        //Runnable
        new Thread(new Thread1(),"aa").start();

        //Callable
        FutureTask<Integer> futureTask1 = new FutureTask<>(new Thread2());
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            return 1024;
        });
    }
}

八、JUC的辅助类

三大辅助类:

  • 减少计数(闭锁 ) :CountDownLatch
  • 循环栅栏 : Cyclic Barrier
  • 信号灯:Semaphore

1.CountDownLatch

方法:

  • await() : 阻塞
  • countDown() :计数器 -1。
  • 当计数器值为0时,因await()方法阻塞的线程将会被执行

实例

CountDownLatch count = new CountDownLatch(6);//创建计数器对象,设置初始值

for(i=0;i<6;i++){
	new Thread(()->{
		sout(...getName);
		count.countDown();//计数器减1
	},String.valueOf(i)).start();
}
count.await();//计数器变为0前,将一直阻塞。记住try-catch
sout("计数器变为0,执行方法");

2.CyclicBarrier

定义:允许一组线程互相等待,到达某个点时不再等待。barrier可以在释放等待线程后重用。
构造方法

  • CyclicBarrier(int parties)
  • CyclicBarrier(int parties,Runnable barrierAction)
  • 最后一个参数为:达到parties后的行为

方法:await() :到达parties前进行等待。

实例

例子: 七龙珠召唤神龙

CyclicBarrier barrier = new(7,()->{
	sout("召唤神龙");
});

for(i=0;i<50;i++){
	new Thread(()->{
		sout("收集~~");
		barrier.await();//记住等待
	});
}

3.Semaphore

信号灯:在获取许可前,线程一直被阻塞,除非线程被中断。
方法

  • acquire() :获取许可
  • acquire(int permits) :获取一定量的许可
  • release():释放一个许可

构造方法
Semaphore(int permits)
Semaphore(int permits,boolean fair):公平设置

实例

停车模型,6车3车位

//创建信号量
Semaphore semaphore = new Semaphore(6);
for(int i = 0;i < 6;i++){
	new Thread(()->{
		try{
			//得到许可
			semaphore.acquire();
			sout(getName()+"抢到了车位");
			TimeUnit.SECONDS.sleep(new Rondom().nextInt(5));
			sout(getName()+"离开了车位");	
		}catch(){}
		finally{
			//释放许可
			semaphore.release();
		}
	},String.valueOf(i)).start();
}

九、JUC读写锁

1.乐观锁与悲观锁

悲观锁: 线程对操作的对象上锁,其他线程只能是阻塞或等待(不支持并发操作)(效率低)
乐观锁: 对修改操作进行版本更新。
修改:

  1. 先核对版本号是否与数据库一致
  2. 是,核对前后数据是否进行了更改
  3. 是,更新数据并更新版本

详情见mysql数据库相关知识。

2.读写锁

读锁:共享锁
写锁:排他锁,独占锁(有写操作都需要等待)

无锁:读写无锁,详见事务隔离。
PS:内部类访问外部数据只能访问常量:加final修饰。

锁降级 rwx

将写锁降级为读锁
jdk8:获取写锁->获取读锁->释放写锁->释放读锁
实现:

//创建读写锁
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

//锁降级
writeLock.lock();
readLock.lock();
writeLock.unlock();
readLock.unlock();

十、阻塞队列

1.概述

一个共享队列,当队列被放满,或者取空,再次执行put或者take时,线程会被阻塞。

2.分类

1.ArrayBlockingQueue:基于数组实现的阻塞队列(定长)
2.LinkedBlockingQueue:基于链表实现的阻塞队列,大小默认为 integer.MAX_VALUE
3.DelayQueue:延迟队列。

  • 只有当指定的延迟时间到了,才能从队列中获取到元素
  • 没有大小限制
  • 使用优先级队列实现的无界阻塞队列
  1. priorityBlockingQueue:支持优先级排队
  2. SynchronousQueue:不存储元素,队列有单个元素
  3. LinkedTransferQueue:由链表组成的双向阻塞队列

3.核心方法

方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove(e)polltake()poll(time,unit)
检查element()peek()XX

十一、线程池

1.概述

线程池维护多个线程。避免短时间内线程频繁的创建和销毁。保证内核充分应用,防止过度调度。
优点:统一管理、减少资源消耗、提升响应速度。

2.使用

通过Executor框架实现,提供Executors工具类。

ExecutorService pool = Executors.newFixedThreadPool(5);
					   Executors.newSingleThreadExecutor();
					   Executors.newCachedThreadPool();
pool.execute(()->{
	...
});
pool.shutDown();

3.分类

一池N线程:Executors.newFixedThreadPool(int);
特点:

  • 固定长度线程池
  • 线程处于一定量,可以很好控制线程并发量
  • 可以重复被使用,在显式关闭之前,将一直存在
  • 超过一定量的线程被提交时,需要在队列中等待

一池一线程:Executors.newSingleThreadExecutor();
特点:

  • 一次处理一个线程
  • 多的等待

可扩容线程池:Executors.newCachedThreadPool();
特点:

  • 可扩容,线程数量不断变化

前面三种提供的创建线程池的方法,一般都不使用,都有局限性,一般采用自定义创建线程池。
原因:

  • FixedThreadPool和SingleThreadPool允许请求队列的长度为 Integer.MAX_VALUE,可能堆积大量请求,造成OOM。
  • CachedThreadPool和SchduledThreadPool允许创建大量线程,造成OOM。

4.自定义线程池

以上提到的创建线程方法,实际都是自定义线程池:new ThreadPoolExecutor

4.1.自定义线程池参数

自定义线程的参数总共有7个参数。

ThreadPoolExecutor(	int corePoolSize; //核心线程数量
					int maximumPoolSize, //最大线程数
					long keepAliveTime, //线程存活时间
					TimeUnit unit,
					BlockingQueue<Runnable> workQueue, //阻塞队列
					ThreadFactory threadfactory, //线程工厂
					RejectedExecutionHandler handler //拒绝策略
)

4.2.底层工作流程

在这里插入图片描述

4.3.四种拒绝策略

AbortPolicy(默认):直接抛出异常阻止系统正常运行。
CallerRunsPolicy:“调用者运行”,不抛弃任务也不抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交任务。
DiscardPolicy:默默丢弃无法处理的任务,不予任务任何处理也不抛出异常,如果允许任务丢失,这是最好的策略。

十二、Fork/Join分支合并框架

Fork/join框架可以将一个大的任务拆分为多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果进行输出。

1.使用

创建ForkJoinPool,调用ForkJoinTask<>方法,继承RecursiveTask<>

class Fibo extends RecursiveTask<Integer>{
	final int n;
	Fibo(int n){
		this.n = n;
	}
	Integer compute(){
		if(n<1){
			return n;
		}
		Fibo f1 = new Fibo(n-1);
		f1.fork();
		Fibo f2 = new Fibo(n-2);
		return f2.compute()+f1.join();
	}
}

例如:从1+2+…+100
使用二分查找思想。相加的两个数不超过10,超过10拆分。

class Mytask extends Recursive<Integer>{
		private static final VALUE = 10;
		private int begin;
		private int end;
		private result;
		public MyTask(int begin,int end){
			this.begin = begin;
			this.end = end;
		}

		protected Integer compute(){
			if((end-begin)<=VALUE){
				for(int i=begin;i<=end;i++){
					result+=i;
				}
			}else{
				int middle = (begin+end)/2;
				Mytask task1 = new Mytask(begin, middle);
				Mytask task2 = new Mytask(middle+1,end);
				task1.fork();
				task2.fork();
				result=task1.join()+task2.join();
				return result;	
			}
			return result;
		}
}

public class test{
	public static void main(String[] args){
		Mytask task = new Mytask(1,100);
		//创建分支合并池
		ForkJoinPool pool = new ForkJoinPool();
		ForkJoinTask<Integer> ForkJoin = ForkJoinPool.submit(task);
		//获取结果
		Integer result = forkJoin.get();
		//关闭
		pool.shutDown();
	}
}

十三、异步回调(CompletableFuture)

//无返回值
CompletableFuture<void> future1 = CompletableFuture.runAsync(()->{...});
future1.get();

//有返回值
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(()->{
	sout(...);
	return 1024;
});
future2.whenComplete((t,u)->{
	sout(t+u);// t为返回值,u为异常
}).get();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值