简介: 在进行性能优化的时候进程会使用多线程,通过多线程并行执行的方式利用物理机器的多核心优势,提升程序的执行速度。
一般我们使用多线程的方式要么是new Thread()创建线程,或者是使用线程池,一般会使用线程池而不直接通过new Thread()的方式来创建线程,因为通过new Thread()的方式创建线程,因为创建线程的行为同样是比较消耗资源的行为,所以会使用线程池,预先直接初始化好一定数量的线程放在线程池中,需要使用的时候直接从线程池去取而不是直接创建从而提升一定的性能。
那么这里就结合JDK1.8为并发编程提供的一些接口,来实现一些案例,以及使用线程池的过程中应该注意的问题,以及解决方案
一、使用Jdk1.8并发包下提供的类进行并发编程
CompleableFuture的使用
1、在你的程序中如果需要一异步执行的时候,一般我们的写法是:
这种的写法,代码多了之后也是太优雅,而且控制起来也不套方便有问题排查的时候不是太方便,那么就可以使用JDK1.8后并发包给提供的CompletableFuture来做处理如下
2、往往入门案例都是简单的,但是现实中编程的情况往往是复杂的
2.1、当需要等待所有的异步线程都执行完毕的时候,将所有异步执行的结果进行汇总时的时候
这个时候就可以将获取这三部分信息进行汇总,示例代码如下:
2.2如果下一步的执行要依赖上一步异步执行的结果呢?
//如果多个异步执行,如果下一个异步程序要依赖上一个的结果怎么办呢?
//比如一个人要想娶媳妇,那就必须到达法定年龄 哈哈哈
/** 按照顺序步骤进行执行的 將上一个 线程的执行结果传入下一个线程中使用 **/
/*CompletableFuture stringCompletableFuture = CompletableFuture.
/**上述用法 还有一个相近的方法 thenCompose **/
/**说明:thenApply 相比于 thenCompose,
thenApply需要返回的类型必须前后一致 ,
是thenCompose 则可以最终返回最后一次执行的异步方法的类型**/
//比如 如下的程序 可以利用第一步的结果在第二步中使用,但是第二步可以返回和第一步骤中不一样的类型
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.<String>supplyAsync(() -> {
return "第一步->长大到法定年龄";
}).thenCompose(th1 -> CompletableFuture.<Integer>supplyAsync(() -> {
System.out.println(th1);
return 50;
}));
2.3 CompleableFuter对两个任务的合并操作:
/** 一个事情分两个步骤 这两个步骤可以同时进行 然后进行结果合并的 相当于是 and 操作 **/
CompletableFuture<String> stringCompletableFuture1 = CompletableFuture.<Object>supplyAsync(() -> {
System.out.println("-1-");
return "事情1";
}).thenCombineAsync(
CompletableFuture.supplyAsync(() -> {
System.out.println("-2-");
return "事情2";
})
, (th1, th2) -> {
return th1 + th2;
}
);
String s = stringCompletableFuture1.get();
/** 当任务一和任务二执行完 然后在执行一个操作 不用最终返回值 这也是相当于是 and **/
CompletableFuture.<String>supplyAsync(() -> {
System.out.println("执行任务一");
return "777777777";
}).runAfterBoth(CompletableFuture.<Integer>supplyAsync(() -> {
System.out.println("执行任务二");
return 6666;
}), () -> {
System.out.println("任务一和任务二执行完再执行这里");
});
三、上边举例了不少 CompleableFuter的使用例子,还有很多大家可以自行从网上的博客查找资料,那么使用的过程中会不会有什么问题呢,这才是我想分享给大家的呢
答案是有的
CompleableFuter 是可以使用线程池,试用线池可以在并发情况下获得更好的性能,但是一旦发生异步的嵌套但是使用的是同一个线程池的时候 就可能货发生死锁的情况,大家看下边的代码:
package com.线程案例.compleable;
import java.util.concurrent.*;
/**
* @author: LCG
* @date: 2022-08-06 19:56:06
* @description:
**/
public class YYYYY {
private static ThreadPoolExecutor threadPoolExecutor=
new ThreadPoolExecutor(1,2,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4));
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("==============执行业务");
try {
/**在这里在进行一个异步调用的程,使用同一个线程池**/
YYYYY.tttt();
//用休眠模拟业务的执行
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
return "888880";
}, threadPoolExecutor);
String s = stringCompletableFuture.get();
}
public static void tttt() throws ExecutionException, InterruptedException {
CompletableFuture<String> t1 = CompletableFuture.supplyAsync(() -> {
System.out.println("======执行t1=====");
try {
//用休眠模拟业务的执行
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "5555";
}, threadPoolExecutor);
CompletableFuture<String> t2 = CompletableFuture.supplyAsync(() -> {
System.out.println("======执行t2=====");
try {
//用休眠模拟业务的执行
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "3333";
}, threadPoolExecutor);
CompletableFuture<String> t3 = CompletableFuture.supplyAsync(() -> {
System.out.println("======执行t3=====");
try {
//用休眠模拟业务的执行
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "3333";
}, threadPoolExecutor);
CompletableFuture.allOf(t1, t2,t3).join();
t1.get();
t2.get();
t3.get();
System.out.println("=======执行完毕======");
}
}
上述代码在运行的时候因为 main里边占用一个线程, tttt() 方法需要三个线程,根据线程池的特点,看上述代码,tttt()中的三个任务要被放到队列中去,只要队列不被放满就不会创建新的线程,那么main中的代码因为要等待返回值就会一直阻塞,造成死锁,这时候你要是使用LinkedBlockQueue依然会发生这种死锁的问题。
你是不是可能会有个疑问 “为什么不调整一下核心线程数和最大线程数,将核心线程数和最大线程数设置一致且让他们大于1,是不是就不会发生死锁了?”
答案:如果是上述的代码并且是没有并发执行main里边的代码的话,是不会有问题的,但是一旦并发执行main的代码上述的说法也是站不住脚的,为什么呢?
因为并发会让main里边的线程都占满线程池里边的所有线程那么tttt()中的线程就会进入等待队列里永远得不到执行从而发生问题。
那么解决办法是什么呢?
如果发生上述的那种多线程使用线程池并且发生父线程嵌套子线程的情况的话就使用让父子线程中使用不同的线程池就可以解决问题了。
代码如下:
package com.线程案例.compleable;
import java.util.concurrent.*;
/**
* @author: LCG
* @date: 2022-08-06 19:56:06
* @description:
**/
public class YYYYY {
private static ThreadPoolExecutor threadPoolExecutor=
new ThreadPoolExecutor(1,2,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4));
private static ThreadPoolExecutor threadPoolExecutor2=
new ThreadPoolExecutor(1,2,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4));
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("==============执行业务");
try {
/**在这里在进行一个异步调用的程,使用同一个线程池**/
YYYYY.tttt();
//用休眠模拟业务的执行
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
return "888880";
}, threadPoolExecutor);
String s = stringCompletableFuture.get();
}
public static void tttt() throws ExecutionException, InterruptedException {
CompletableFuture<String> t1 = CompletableFuture.supplyAsync(() -> {
System.out.println("======执行t1=====");
try {
//用休眠模拟业务的执行
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "5555";
}, threadPoolExecutor2);
CompletableFuture<String> t2 = CompletableFuture.supplyAsync(() -> {
System.out.println("======执行t2=====");
try {
//用休眠模拟业务的执行
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "3333";
}, threadPoolExecutor2);
CompletableFuture<String> t3 = CompletableFuture.supplyAsync(() -> {
System.out.println("======执行t3=====");
try {
//用休眠模拟业务的执行
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "3333";
}, threadPoolExecutor2);
CompletableFuture.allOf(t1, t2,t3).join();
t1.get();
t2.get();
t3.get();
System.out.println("=======执行完毕======");
}
}
看官们看懂了吗?这种情况稍有不慎就会发生的自己的代码里,就会酿成生产事故。
您的进步是我最大的开心,坚持分享快乐多多。