目录
3.2.3、继承Thread类+实现Runnable接口区别
3.2.5、实现Callable接口和实现Runnable接口区别
3.5.4、Synchronized和Lock(ReentrantLock)区别
3.6.1、wait()、notify()、notifyAll()
1、程序、进程、线程
程序:一段静态的代码块。
进程:正在运行的程序,资源分配的基本单位。每个进程都会分配不同的内存区域
线程:
a、一个程序内部的一条执行路径。一个进程可以有多个线程。
b、每个线程拥有独立的运行栈和程序计数器。
c、一个进程的多个线程共享相同的内存单元/内存地址空间,使得进程间通信更简便、高效。但是多个线程操作共享的资源可能会带来安全的隐患。
d、一个java应用程序java.exe,其实至少有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程
2、并行、并发
并行:多个CPU同时执行多个任务(多个人同时做不同的事)
并发:一个CPU(采用时间片)同时执行多个任务(多个人做同一件事)
3、多线程
3.1、优点
对于单核CPU,只使用单个线程先后完成多个任务比多个线程来完成用的时间更短,因为线程之间切换需要时间。如果是多核肯定多线程更快。那为何还要使用多线程?
a、提高应用程序的响应。对图形化界面更有意义,可增强用户体验
b、提高计算机系统CPU的利用率
c、改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和
修改
3.2、实现方式
4种方式:继承Thread类、实现Runnable接口、实现Callable接口、线程池
3.2.1、继承Thread类
// 1、创建一个继承于Thread类的子类
public class ThreadTest1 extends Thread {
// 2、重写Thread类的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程" + i);
}
}
public static void main(String[] args) {
// 3、创建thred类的子类的对象
ThreadTest1 threadTest1 = new ThreadTest1();
// 4、调用start方法:1、启动当前线程 2、调用当前线程的run方法
threadTest1.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程" + i);
}
}
}
// 其它写法
public class ThreadTest1 extends Thread {
public static void main(String[] args) {
// 创建Thread类的匿名子类的写法
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程1:" + i);
}
}
}.start();
// lambda表达式写法
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("子线程2:" + i);
}
}).start();
}
}
Thread类常用方法:
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程名称
- static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
- static void yield():线程让步暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法
- join(): 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止低优先级的线程也可以获得执行
- static void sleep(long millis) :(指定时间:毫秒)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。抛出InterruptedException异常
- stop(): 强制线程生命期结束,不推荐使用
- boolean isAlive(): 返回boolean,判断线程是否还活着
线程优先级:MAX_PRIORITY :10 MIN _PRIORITY :1 NORM_PRIORITY:5
注意:低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
- getPriority() : 返回线程优先值
- setPriority(int newPriority) : 改变线程的优先级
3.2.2、实现Runnable接口
// 1、创建一个实现Runnable接口的类
public class ThreadTest2 implements Runnable {
// 2、重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程" + i);
}
}
public static void main(String[] args) {
// 3、创建实现类
ThreadTest2 threadTest2 = new ThreadTest2();
// 4、将对象作为参数传入构造器中
Thread thread = new Thread(threadTest2);
// 5、调用start() 1、启动线程 2、调用当前线程的run()方法
/* 当前线程指的是Thread,而ThreadTest2是实现Runnable接口。为什么能调用run方法?
源码中new Thread(target)时调用Runnable类型的target的run()*/
thread.start();
}
}
3.2.3、继承Thread类+实现Runnable接口区别
区别:
优先选择实现Runnable接口去创建多线程。(实际开发是用线程池去创建)
1、因为java是类是单继承,如果当前对象本身就继承了它的父类,那么它就不能使用继承Thread类的方式去创建线程
2、使用继承Runnable接口不用创建多个对象可以实现数据共享,定义的成员变量也不需要添加static去修饰
// 继承Thread类实现卖票案例
// 注意:1、存在线程安全问题:多人共享一张票(后期加锁解决)或者票号为0和负数
// 2、控制台打印输出的ticket顺序不是依次递减的。其实ticket是递减的,只是打印语句时可能存在延时等问题
public class ThreadTicket1 extends Thread{
// 必须设置为static。不然每个对象调用都会拥有自己的ticket=100;设置为static才能共享一个
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
//不加也可能会发生线程安全问题,只是加了使概率增大更直观看出来
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"票号为:"+ticket);
ticket--;
} else {
break;
}
}
}
public static void main(String[] args) {
ThreadTicket1 threadTicket1 = new ThreadTicket1();
ThreadTicket1 threadTicket2 = new ThreadTicket1();
ThreadTicket1 threadTicket3 = new ThreadTicket1();
threadTicket1.setName("窗口A");
threadTicket2.setName("窗口B");
threadTicket3.setName("窗口C");
threadTicket1.start();
threadTicket2.start();
threadTicket3.start();
}
}
// 实现Runnable接口实现卖票案例
// 注意:1、存在线程安全问题:多人共享一张票(后期加锁解决)或者票号为0和负数
// 2、控制台打印输出的ticket顺序不是依次递减的。其实ticket是递减的,只是打印语句时可能存在延时等问题
public class ThreadTicket2 implements Runnable{
// 不用设置为static。因为只有一个对象去共享ticket
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
// 这里直接使用getName()无效===this.getName()
// 因为当前对象ThreadTicket2并没有继承Thread类,所以无法调用getName()方法。可以使用Thread.currentThread()
System.out.println(Thread.currentThread().getName()+"票号为:"+ticket);
ticket--;
} else {
break;
}
}
}
public static void main(String[] args) {
ThreadTicket2 threadTicket2 = new ThreadTicket2();
// 创建多个线程,使用一个对象
Thread thread1 = new Thread(threadTicket2);
Thread thread2 = new Thread(threadTicket2);
Thread thread3 = new Thread(threadTicket2);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread3.setName("窗口C");
thread1.start();
thread2.start();
thread3.start();
}
}
3.2.4、实现Callable接口(jdk5.0新增)
//1、创建一个实现Callable接口的实现类
public class ThreadTest3 implements Callable<Integer> {
// 2、重写call()方法,可以有返回值
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
System.out.println("子线程" + i);
sum += i;
}
return sum;
}
public static void main(String[] args) {
// 3、创建实现类
ThreadTest3 threadTest3 = new ThreadTest3();
// 4、创建FutureTask对象
FutureTask<Integer> futureTask = new FutureTask<>(threadTest3);
Integer sum = null;
// 5、调用start()
new Thread(futureTask).start();
try {
// 6、获取Callable中call的返回值
// get()方法的返回值即为FutureTask构造参数Callable实现类重写的call()的返回值
sum = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(sum);
}
}
3.2.5、实现Callable接口和实现Runnable接口区别
区别:
1、callable接口的run()方法可以有返回值
2、方法可以抛出异常
3、支持泛型的返回值
4、需要借助FutureTask类,比如获取返回结果
3.3、生命周期
3.4、线程同步
优点:解决线程的安全问题
缺点:只有一个线程参与,其它线程等待,效率低。
3.4.1、同步代码块(解决线程安全问题)
// 使用同步代码块解决线程安全问题(实现Runnable接口方式)
// 问题:存在线程安全问题:出现重票、错票问题(共享数据)
// 原因:当前线程操作未完成,其它线程进来进行了操作
/*
synchronized(同步监视器) { //同步监视器:相当于锁,任何一个对象都可以充当锁。多个线程必须共用同一把锁
// 需要被同步的代码
}
*/
public class ThreadTicket3 implements Runnable {
private int ticket = 100;
Object object = new Object();
@Override
public void run() {
while (true) {
/*放在这线程会出现不安全,因为不是共用同一把锁。线程执行调用的是run方法。会创建多个object
Object object = new Object();*/
//synchronized (object) {
synchronized (this) { // 一般使用this表示当前对象,ThreadTicket3对象只有一个
if (ticket > 0) {
try {
//不加也可能会发生线程安全问题,只是加了使概率增大更直观看出来
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public static void main(String[] args) {
ThreadTicket3 threadTicket3 = new ThreadTicket3();
// 创建多个线程,使用一个对象
Thread thread1 = new Thread(threadTicket3);
Thread thread2 = new Thread(threadTicket3);
Thread thread3 = new Thread(threadTicket3);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread3.setName("窗口C");
thread1.start();
thread2.start();
thread3.start();
}
}
// 使用同步代码块解决线程安全问题(继承Thread类方式)
public class ThreadTicket4 extends Thread {
// 必须设置为static。不然每个对象调用都会拥有自己的ticket=100;设置为static才能共享一个
private static int ticket = 100;
// 因为创建了多个对象就不能,每个对象都有自己的锁,所以要加static
private static Object object = new Object();
@Override
public void run() {
while (true) {
// synchronized (this) { 不能使用this,ThreadTicket4对象有多个
// synchronized (ThreadTicket4.class) { 可以使用类.class方式
synchronized (object) {
if (ticket > 0) {
try {
Thread.sleep(100);//可能会出现票号为负数的情况
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public static void main(String[] args) {
ThreadTicket4 threadTicket1 = new ThreadTicket4();
ThreadTicket4 threadTicket2 = new ThreadTicket4();
ThreadTicket4 threadTicket3 = new ThreadTicket4();
threadTicket1.setName("窗口A");
threadTicket2.setName("窗口B");
threadTicket3.setName("窗口C");
threadTicket1.start();
threadTicket2.start();
threadTicket3.start();
}
}
3.4.2、同步方法(解决线程安全问题)
// 使用同步方法解决线程安全问题(实现Runnable接口方式)
/*
// 同步监视器依然存在,只是我们不需要显示声明
非静态方法:同步监视器:this
静态方法:同步监视器:当前类本身
public synchronized void 方法名 (参数){
}
*/
public class ThreadTicket5 implements Runnable {
private int ticket = 100;
@Override
public void run() {
// while不能在同步方法,会造成一个线程先循环完在执行下一个线程
while (true) {
show();
}
}
// 同步方法方式
private synchronized void show() {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
ticket--;
}
}
public static void main(String[] args) {
ThreadTicket5 threadTicket5 = new ThreadTicket5();
// 创建多个线程,使用一个对象
Thread thread1 = new Thread(threadTicket5);
Thread thread2 = new Thread(threadTicket5);
Thread thread3 = new Thread(threadTicket5);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread3.setName("窗口C");
thread1.start();
thread2.start();
thread3.start();
}
}
// 使用同步方法解决线程安全问题(继承Thread类)
public class ThreadTicket6 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
// 同步方法方式
// private synchronized void show(){ // 当前对象有多个会出现线程安全问题
private static synchronized void show(){
//此时的同步监视器并不是this(this和static不能共存),而是ThreadTicket6.class
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
ticket--;
}
}
public static void main(String[] args) {
ThreadTicket6 threadTicket1 = new ThreadTicket6();
ThreadTicket6 threadTicket2 = new ThreadTicket6();
ThreadTicket6 threadTicket3 = new ThreadTicket6();
threadTicket1.setName("窗口A");
threadTicket2.setName("窗口B");
threadTicket3.setName("窗口C");
threadTicket1.start();
threadTicket2.start();
threadTicket3.start();
}
}
3.5、线程死锁
3.5.1、概念
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
3.5.2、死锁示例
public class DeadLockTest {
public static void main(String[] args) {
StringBuffer b1 = new StringBuffer();
StringBuffer b2 = new StringBuffer();
// 继承Thread类匿名内部类写法
new Thread() {
@Override
public void run() {
synchronized (b1) {
b1.append(1);
b2.append("a");
try {
Thread.sleep(100);//增大死锁的可能信
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b2) {
b1.append(2);
b2.append("b");
System.out.println(b1);
System.out.println(b2);
}
}
}
}.start();
// 实现Runnable接口匿名内部类写法
new Thread(new Runnable() {
@Override
public void run() {
synchronized (b2) {
b1.append(3);
b2.append("c");
try {
Thread.sleep(100);//增大死锁的可能信
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b1) {
b1.append(4);
b2.append("d");
System.out.println(b1);
System.out.println(b2);
}
}
}
}).start();
}
}
3.5.3、Lock锁(解决线程安全问题)
a、Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
b、ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
public class LockThread implements Runnable {
private int ticket = 100;
//1、实例化ReentrantLock对象
ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 2、调用lock()
reentrantLock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
ticket--;
} else {
break;
}
} finally {
// 3、调用解锁方法unlock()
reentrantLock.unlock();
}
}
}
public static void main(String[] args) {
LockThread threadTicket2 = new LockThread();
// 创建多个线程,使用一个对象
Thread thread1 = new Thread(threadTicket2);
Thread thread2 = new Thread(threadTicket2);
Thread thread3 = new Thread(threadTicket2);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread3.setName("窗口C");
thread1.start();
thread2.start();
thread3.start();
}
}
3.5.4、Synchronized和Lock(ReentrantLock)区别
相同点:
都可以解决线程的安全问题
不同点:
1、syn是关键字 ----- lock是类
2、syn自动加锁和释放锁 ----- lock必须用lock()和unlock()
3、syn是非公平锁 ---- lock通过创建对象构造函数参数是否为true觉得是否为公平锁(默false)
4、syn不可响应中断,一个线程获取不到锁就一直等着 ----- lock可以响应中断
5、syn不可限时 ---- lock可限时,通过trylock()可避免死锁
3.6、线程通信
3.6.1、wait()、notify()、notifyAll()
注意:
- 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
- 这三个方法的调用者必须是synchronized方法或synchronized代码块中的同步监视器
- 这三个方法是定义在java.lang.Object类中
- wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
// 两个线程交互打印
public class communicationThread implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (number <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
// 调用wait()方法的线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
public static void main(String[] args) {
communicationThread threadTicket2 = new communicationThread();
// 创建多个线程,使用一个对象
Thread thread1 = new Thread(threadTicket2);
Thread thread2 = new Thread(threadTicket2);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread1.start();
thread2.start();
}
}
3.6.2、sleep()和wiat()
相同点:
都可以使当前的线程进入阻塞状态
不同点:
1、sleep是Thread类的方法,wait是Object类的方法
2、sleep指定当前线程阻塞,时间到达后进入就绪状态,不会释放锁。wait会释放锁
3、sleep方法会抛异常(interrupted exception),wait不会
4、sleep方法在任何地方可以使用,wait方法只能在同步方法或者同步代码块中·
3.7、线程池
3.7.1、思路
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。所以需要使用线程池去处理。
提前 创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
3.7.2、优点
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
3.7.3、七大参数和工作顺序
七大参数:
1、corePoolSize:核心线程数。线程池创建好就等待执行异步任务的线程数量。一直存在除非设置了allowCoreThreadTimeOut
2、maximumPoolSize:最大线程数量。控制资源
3、keepAliveTime:空闲线程存活时间。当线程数>核心线程数,如果线程空闲时间>指定的存活时间,那么就会释放空闲的线程(maximumPoolSize-corePoolSize)
4、unit:时间单位
5、BlockingQueue<Runnable> workQueue:阻塞队列。如何异步任务(线程)很多,就会将多的任务放到队列中,如果线程空闲,就会去队列取出新的任务继续执行
6、threadFactory:线程的创建工厂
7、RejectedExecutionHandler handler:如果队列满了,按照指定拒绝策略拒绝执行任务
工作顺序:
1、创建线程池,初始化化核心线程数,准备接受任务
2、工作线程数<=核心线程数。正常执行任务
3、工作线程数>核心线程数。多出的任务放到阻塞队列中,空闲的核心线程就会自己去阻塞队列中获取任务执行
4、队列满了,工作线程数量<最大线程数量。直接开启新的线程执行
5、最大线程数量都执行好了,空闲的线程会在keepAliveTime指定的时间后释放maximumPoolSize-corePoolSize线程数量,最终保持到核心线程的大小
6、工作线程数量>最大线程数量。依然有新任务进来。会执行拒绝策略进行处理
拒绝策略:
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
DiscardOldestPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务(可以选择不抛弃)
3.7.3、相关API
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
- <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
- void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():可缓存的线程池;线程池大小完全依赖于操作系统能够创建的最大线程大小
- Executors.newFixedThreadPool(n); 固定数量的线程池;每提交一个任务就是一个线程, 直到达到线程此的最大数量(max=core)
- Executors.newSingleThreadExecutor() :单线程的线程池;线程池中每次只有一个线程在工作。单线程串行执行任务
- Executors.newScheduledThreadPool(n):一个大小无限的线程池;支持定时以及周期性执行任务的需求
// 创建线程的方式:使用线程池
public class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
// 一个线程池:core 7、max 20、queue 50 100个并发进来如何分配
// 7个会立即执行,50个进入队列,再开13个进行执行。剩下的30个使用拒绝策略执行
class ThreadPool {
public static void main(String[] args) {
// 方式一:
// 1、提供指定线程数量的线程池(如果不用默认的则使用步骤2进行修改)
ExecutorService executorService = Executors.newFixedThreadPool(7);
// 2、设置属性(ExecutorService是个接口,设置属性需要它的实现类ThreadPoolExecutor)
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
threadPoolExecutor.setMaximumPoolSize(20);
// 方式二:
ThreadPoolExecutor threadPoolExecutor1 = new ThreadPoolExecutor(
7,
20,
10,
TimeUnit.SECONDS,
/*
根据情况选择不同的队列,LinkedBlockingQueue默认Integer.MAX_VALUE,线程过多会使的内存被占满
这里的大小可以根据压测的最大值进行设置
*/
new LinkedBlockingQueue<>(50),
Executors.defaultThreadFactory(),
// 根据情况选择不同的拒绝策略
new ThreadPoolExecutor.AbortPolicy()
);
// 3、执行指定线程的操作。需要提供时实现Runnable接口或Callable接口实现类的对象
executorService.execute(new NumberThread());// 适合适用于Runnable接口
//executorService.submit(Callable callable);// 适合适用于Callable接口
// ....
// 4、关闭连接池
executorService.shutdown();
}
}
3.8、CompletableFuture异步编排
概念: 线程B依赖线程A执行后的返回结果。可以用异步编排实现
- CompletableFuture 类实现了 Future 接口,所以你还是可以像以前一样通过`get`方法阻塞或者轮询的方式获得结果,但是这种方式不推荐使用。
- CompletableFuture 和 FutureTask 同属于 Future 接口的实现类,都可以获取线程的执行结果。
3.8.1、创建异步对象
创建异步对象
static CompletableFuture<Void> runAsync(Runnable runnable)
static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
注意:a、runXxxx 都是没有返回结果的,supplyXxx 都是可以获取返回结果的
b、可以传入自定义的线程池,否则就用默认的线程池;
3.8.2 、计算完成时回调方法
2、计算完成时回调方法
CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)
CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
注意:a、whenComplete 可以处理正常和异常的计算结果,exceptionally 处理异常情况
b、whenComplete 和 whenCompleteAsync 的区别:
whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行c、 方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
public class CompletableTest {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
// runAsync和supplyAsync使用
// 1.无返回值,传入自定义的线程池
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
}, executorService);
// 2.有返回值,传入自定义的线程池
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int i = 10 / 5;
return i;
}, executorService);
// 获取返回值
Integer integer = completableFuture.get();
System.out.println(integer);
// whenComplete和exceptionally使用
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
int i = 10 / 0;
return i;
}, executorService).whenComplete((req, exception) -> {
// 虽然能得到异常信息,但是没法修改返回数据
System.out.println("异步任务完成:结果是" + req + ";异常是:" + exception);
}).exceptionally(throwable -> {
// 可以感知异常,同时返回默认值
return 10001;
});
// 获取返回值
Integer integer1 = completableFuture1.get();
System.out.println(integer1);
// apply使用(方法执行完后的处理)
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
int i = 10 / 0;
return i;
}, executorService).handle((req, thr) -> {
if (null != req) {
return 10002;
}
if (null != thr) {
return 10003;
}
return 10004;
});
// 获取返回值
Integer integer2 = completableFuture2.get();
System.out.println(integer2);
executorService.shutdown();
}
}
3.8.3、handle()方法
handle()方法
<U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
<U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)
<U> CompletableFuture<U> handleAsync( BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)
注意:和 complete 一样,可对结果做最后的处理(可处理异常),可改变返回值。
3.8.4、线程串行化方法
注意:
a、thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
b、thenAccept 方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果c、thenRun 方法:只要上面的任务执行完成,就开始执行 thenRun,只是处理完任务后,执行thenRun 的后续操作
d、带有 Async 默认是异步执行的。同之前。e、以上都要前置任务成功完成。Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型
public class CompletableTest1 {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 线程串行化
* 1、thenRunAsync:异步方式、不能获取上一步的执行结果,无返回值
* 2、thenAcceptAsync:异步方式、能接受上一步返回结果,但是无返回值
* 3、thenApplyAsync:异步方式、既能接受上一步返回结果,有返回值
*/
// thenRunAsync使用
CompletableFuture.supplyAsync(() -> {
int i = 10 / 5;
return i;
}, executorService).thenRunAsync(() -> {
System.out.println("异步任务1启动");
}, executorService);
// thenAcceptAsync使用
CompletableFuture.supplyAsync(() -> {
int i = 10 / 5;
return i;
}, executorService).thenAcceptAsync(res -> {
System.out.println("异步任务2启动"+res);
}, executorService);
// thenApplyAsync
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
int i = 10 / 2;
return i;
}, executorService).thenApplyAsync(res -> {
System.out.println("异步任务3启动" + res);
return res + "修改值";
}, executorService);
System.out.println(future.get());
executorService.shutdown();
}
}
3.8.5、两任务组合 - 都要完成
注意:两个任务必须都完成,触发该任务。
a、thenCombine:组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值
b、thenAcceptBoth:组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值。
c、runAfterBoth:组合两个 future,不需要获取 future 的结果,只需两个 future 处理完任务后,处理该任务。
3.8.6、两任务组合 - 一个完成
public class CompletableTest2 {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 两任务组合都要完成
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
return "hello";
}, executorService);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
return "world";
}, executorService);
// runAfterBothAsync使用---不会获取future1和future2的返回结果、无返回值
future1.runAfterBothAsync(future2,()->{
System.out.println("future1和future2都执行完毕");
},executorService);
// thenAcceptBothAsync---会获取future1和future2的返回结果、无返回值
future1.thenAcceptBothAsync(future2,(f1,f2)->{
System.out.println("future1和future2都执行完毕,future1为:"+f1+";future2为:"+f2);
},executorService);
// thenAcceptBothAsync---会获取future1和future2的返回结果、有返回值
CompletableFuture<String> future3 = future1.thenCombineAsync(future2, (f1, f2) -> {
return f1 + f2;
}, executorService);
System.out.println(future3.get());
executorService.shutdown();
}
}
注意:当两个任务中,任意一个 future 任务完成的时候,执行任务。
a、applyToEither:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值。
b、acceptEither:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值。
c、runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值。
3.8.7、多任务组合
注意:
a、allOf:等待所有任务完成
b、anyOf:只要有一个任务完成
public class CompletableTest3 {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 多任务组合
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1执行");
return "任务1";
}, executorService);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务2执行");
return "任务2";
}, executorService);
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务3执行");
return "任务3";
}, executorService);
/**
* 如果按照之前:future1.get();future2.get();future3.get()
* 需要等待前面的执行完才执行后面的,等待时间过长
* allOf的方式,因为每个线程是异步执行的,所以当全部完成后再执行其它的时间会相对减少
*/
// allOf使用---等待任务执行完成
CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2, future3);
allOf.get();
System.out.println("全部执行完毕;future1:"+future1.get()+";future2"+future2.get()+";future3"+future3.get());
// anyOf使用---只要有一个任务完成
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2, future3);
System.out.println("当前执行完的任务是:"+anyOf.get());
executorService.shutdown();
}
}