详解一道京东面试题
- 跟多精彩请关注公众号“xhJaver”,京东java工程师和你一起成长
多线程并发执行?线程之间通信? 这是我偶然听到我共事做面试官时问的一道题,感觉很有意思,收回来大家和大家探讨下
面试题目形容
当初呢,咱们有三个接口,就叫他A,B,C吧,这三个接口都是查问某个人征信信息的,必须同时返回true,咱们才认为这个人的征信合格,如果其中某一个返回false的话,就表明这个人的征信不合格,如果是你,你会怎么设计怎么写这个代码呢?
第一次思考
首先,肯定是并发执行,如果说A接口执行3秒,B接口执行5秒,C接口执行8秒的话
- 串行执行: 3+5+8 = 16秒
- 并发执行: 8=8秒 (工夫最久的那个接口执行的工夫就是这三个接口的执行总工夫)
相熟的感觉,多线程执行工作,我在第二章文章实战!xhJaver居然用线程池优化了。。。有提过怎么写,感兴趣的读者能够回去看一下,不过我在这里再写一下,话不多说来看下代码
并发代码
倡议用PC端查看,所有代码都可间接复制运行,代码中重要的点都有具体正文
- 首先,咱们先定义这三个接口
public class DoService {
//设置A B C接口的返回值,b接口设置的是false
private static Boolean flagA = true;
private static Boolean flagB = false;
private static Boolean flagC = true;
public static boolean A(){
long start = System.currentTimeMillis();
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
System.out.println("a被打断 耗时" + (System.currentTimeMillis() - start));
e.printStackTrace();
}
System.out.println("a耗时 "+(System.currentTimeMillis() - start));
return flagA;
}
public static boolean B() {
long start = System.currentTimeMillis();
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
System.out.println("b被打断 耗时" + (System.currentTimeMillis() - start));
e.printStackTrace();
}
System.out.println("b耗时 "+(System.currentTimeMillis() - start));
return flagB;
}
public static boolean C() {
long start = System.currentTimeMillis();
try {
Thread.sleep(8000L);
} catch (InterruptedException e) {
System.out.println("c被打断 耗时" + (System.currentTimeMillis() - start));
e.printStackTrace();
}
System.out.println("c耗时 "+(System.currentTimeMillis() - start));
return flagC;
}
}
2 其次 咱们先发明一个Task 工作类
public class Task implements Callable<Boolean> {
private String taskName;
private Integer i;
public Task(String taskName,int i){
this.taskName =taskName;
this.i = i;
}
@Override
public Boolean call() throws Exception {
// 标记 返回值,代表这个接口是否执行胜利
Boolean flag = false;
//记录接口名字
String serviceName = null;
//依据i的值来判断调用哪个接口
if (i==1){
flag = DoService.A();
serviceName="A";
}
if (i==2){
flag = DoService.B();
serviceName="B";
}
if (i==3){
flag = DoService.C();
serviceName="C";
}
System.out.println("以后线程是: "+Thread.currentThread().getName()+"正在解决的工作是: "+this.taskName+"调用的接口是: "+serviceName);
return flag;
}
}
3 最初,咱们定义一个测试类
class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创立一个蕴含三个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
//当时筹备好贮存后果的list汇合
List< Future<Boolean> > list = new ArrayList<>();
//开始计时
long start = System.currentTimeMillis();
for (int i=1;i<4;i++){
Task task = new Task("工作"+i,i);
//将每个工作提交到线程池中,并且失去这个线程的执行后果
Future<Boolean> result = executorService.submit(task);
list.add(result);
}
//记得把线程池敞开
executorService.shutdown();
//定义一个变量 0
int count = 0;
System.out.println("期待处理结果。。。");
for (int i=0;i<list.size();i++){
Future<Boolean> result = list.get(i);
//失去解决的后果 线程阻塞,如果线程没有解决完就始终阻塞
Boolean flag = result.get();
//如果这个接口返回true,那么count就++
if (flag){
count++;
}
}
System.out.println("线程池+后果解决工夫:"+ (System.currentTimeMillis() - start));
//如果count数量为3,那么三个就都为true,代表这个人征信没问题
if (count==3){
System.out.println("合格");
}else {//否则,就是有问题
System.out.println("不合格");
}
}
}
4.咱们看下输入后果
期待处理结果。。。
a耗时 3000
以后线程是: pool-1-thread-1正在解决的工作是: 工作1调用的接口是: A
b耗时 5000
以后线程是: pool-1-thread-2正在解决的工作是: 工作2调用的接口是: B
c耗时 8000
以后线程是: pool-1-thread-3正在解决的工作是: 工作3调用的接口是: C
线程池+后果解决工夫:8008
不合格
- 咱们运行的时候会发现,它的输入后果的程序如下1 2 3 4 5 咱们图中的2,3,4是再线程池内开了三个线程执行的,他们之间相隔一段时间才呈现的,因为每个接口都有执行工夫
程序运行后,“标记2”是3秒后呈现,“标记三”是5秒后呈现,“标记4”是8秒后呈现
其实4和5相差工夫很短,简直是同时呈现的,因为4执行完了就是主线程继续执行了
线程池+后果解决的工夫一共是8秒,而每个接口别离执行的工夫是3秒,5秒,8秒,达到了咱们所说的,多线程解决多个接口,总共耗时工夫是耗时最长的接口的工夫
和京东面试官探讨
波哥说(我爱叫他波哥,西南人,谈话则逗,风趣的人几乎就是世间珍宝,其实我也蛮乏味的,就是没人发现),你这程序不行啊,有个毛病,如果说,你这个A接口,耗时三秒,他返回了false,那么你另外两个线程也不必执行了,这个人的征信曾经不合格了,你须要判断下,如果某一个线程执行的工作返回了false,那么就及时中断其余两个线程
灵光乍现
上一次的代码曾经实现了多线程执行工作,可是这线程间通信怎么办呢?怎么能力依据一个线程的执行后果而打断其余线程呢?我想到了以下几点
-
共享变量
public static volatile boolean end = true;
- 这个共享变量就代表是否完结三个线程的执行 如果为true的话,代表完结,false的话代表不完结线程执行
-
计数器
public static AtomicInteger count =new AtomicInteger(0);
- 每当每个线程执行完的话,如果返回true,计数器就+1,当计数器变为3的时候,就代表这个人征信没问题
-
中断办法
interrupt()
- 咱们会独自开个线程始终循环检测这个变量,当检测到为true的时候,就会调用中断办法中断这三个线程
-
阻塞线程
countDownLatch
- 咱们程序往下执行须要获取后果,获取不到这个后果的话,就要始终等着。咱们能够用这个线程阻塞的工具,一开始给他设置数量为1,当满足持续向下执行的条件时,调用
countDownLatch.countDown();
,在主线程那里countDownLatch.await();
一下这样当检测到数量为0的时候,主线程那里就持续往下执行了,话不多说,来看代码
- 咱们程序往下执行须要获取后果,获取不到这个后果的话,就要始终等着。咱们能够用这个线程阻塞的工具,一开始给他设置数量为1,当满足持续向下执行的条件时,调用
代码优化
倡议用PC端查看,所有代码都可间接复制运行,代码中重要的点都有具体正文
- 首先,还是创立接口
public class DoService {
private static Boolean flagA = true;
private static Boolean flagB = false;
private static Boolean flagC = true;
public static boolean A(){
long start = System.currentTimeMillis();
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
System.out.println("a被打断 耗时" + (System.currentTimeMillis() - start));
e.printStackTrace();
}
System.out.println("a耗时 "+(System.currentTimeMillis() - start));
return flagA;
}
public static boolean B() {
long start = System.currentTimeMillis();
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
System.out.println("b被打断 耗时" + (System.currentTimeMillis() - start));
e.printStackTrace();
}
System.out.println("b耗时 "+(System.currentTimeMillis() - start));
return flagB;
}
public static boolean C() {
long start = System.currentTimeMillis();
try {
Thread.sleep(8000L);
} catch (InterruptedException e) {
System.out.println("c被打断 耗时" + (System.currentTimeMillis() - start));
e.printStackTrace();
}
System.out.println("c耗时 "+(System.currentTimeMillis() - start));
return flagC;
}
}
2 创立工作
public class Task implements Runnable {
private String name ;
public Task( String name){
this.name = name;
}
@Override
public void run() {
boolean flag = false;
String serviceName = null;
if(this.name.equals("A")){
serviceName = "A";
flag = DoService.A();
}
if(this.name.equals("B")){
serviceName = "B";
flag = DoService.B();
}
if(this.name.equals("C")){
serviceName = "C";
flag = DoService.C();
}
//如果有一个为false
if (!flag){
//就把共享标记地位为false
Test.end = false;
}else {
//计数器加一,到三的话就是三个都为true
Test.count.incrementAndGet();
}
System.out.println("以后线程是: "+Thread.currentThread().getName()+"正在解决的工作是: "+this.name+"调用的接口是: "+serviceName);
}
}
3 创立测试类
class Test {
//设置countDownLatch 外面计数为1,
// 只调用一次countDownLatch.countDown就能够继续执行 countDownLatch.await();
//前面的代码了,接触阻塞
public static CountDownLatch countDownLatch = new CountDownLatch(1);
//默认都为true,有一个线程为false了,那么就变为false
public static volatile boolean end = true;
//计数器,数字变为3的时候代表三个接口都返回true,线程平安的原子类
public static AtomicInteger count =new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
//创立三个工作,分被调用A B C 接口
Task taskA = new Task("A");
Task taskB = new Task("B");
Task taskC = new Task("C");
//创立三个线程
Thread tA = new Thread(taskA);
Thread tB = new Thread(taskB);
Thread tC = new Thread(taskC);
//开启三个线程
tA.start();
tB.start();
tC.start();
//在开启一个线程,这个线程就是独自循环扫描这个共享变量的
new Thread(new Runnable() {
@Override
public void run() {
//此线程始终循环判断这个完结变量,如果为false的话,就代表有一个接口返回false,跳出,重点其余线程
while (true){
if (!end ){
//当这个共享变量为false时i示意,其余线程能够中断了,所以就打断他们执行
tA.interrupt();
tB.interrupt();
tC.interrupt();
//如果某个线程被打断的话,就表明不合格
System.out.println("不合格");
//countDownLatch 计数器减一
countDownLatch.countDown();
break;
}
if (Test.count.get()==3){
System.out.println("合格");
//countDownLatch 计数器减一
countDownLatch.countDown();
break;
}
}
}
}).start();
System.out.println(Thread.currentThread().getName()+"主线程开始挂起");
//阻塞主线程继续执行,期待其余线程计算完后果在执行上来,countDownLatch中的计数为0时,就能够继续执行上来
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 主线取得后果后继续执行"+(System.currentTimeMillis() - start));
}
}
4 咱们看下输入后果
main主线程开始挂起
a耗时 3024
以后线程是: Thread-0正在解决的工作是: A调用的接口是: A
b耗时 5000
以后线程是: Thread-1正在解决的工作是: B调用的接口是: B
c被打断 耗时5001
不合格
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.xhj.concurrent.executor_05._02.DoService.C(DoService.java:41)
at com.xhj.concurrent.executor_05._02.Task.run(Task.java:30)
at java.lang.Thread.run(Thread.java:748)
c耗时 5003
以后线程是: Thread-2正在解决的工作是: C调用的接口是: C
main 主线取得后果后继续执行5014
- 咱们运行的时候会发现
由图可见,咱们首先就把主线程挂起,期待其余四个线程的处理结果,三个线程别离解决那三个接口,另外一个线程循环遍历那个共享变量,当检测到为false时,及时打断其余线程,这样的话,就解决了下面的那个问题
- 跟多精彩请关注公众号“xhJaver”,京东java工程师和你一起成长
往期举荐
- 不会吧,就是你把线程池讲的这么分明的?
- ?线程池为什么能够复用,我是蒙圈了。。。
- 实战!xhJaver居然用线程池优化了。。。
- mysql能够靠索引,而我只能靠打工,加油,打工人!
发表回复