乐趣区

关于java:京东这道面试题你会吗

详解一道京东面试题

  • 跟多精彩请关注公众号“xhJaver”,京东 java 工程师和你一起成长

多线程并发执行?线程之间通信?这是我偶然听到我共事做面试官时问的一道题,感觉很有意思,收回来大家和大家探讨下

面试题目形容

当初呢, 咱们有三个接口,就叫他 A,B,C 吧,这三个接口都是查问某个人征信信息的,必须同时返回true, 咱们才认为这个人的征信合格,如果其中某一个返回 false 的话,就表明这个人的征信不合格,如果是你,你会怎么设计怎么写这个代码呢?

第一次思考

首先,肯定是并发执行,如果说 A 接口执行 3 秒,B 接口执行 5 秒,C 接口执行 8 秒的话

  • 串行执行: 3+5+8 = 16 秒
  • 并发执行: 8= 8 秒(工夫最久的那个接口执行的工夫就是这三个接口的执行总工夫)

相熟的感觉,多线程执行工作,我在第二章文章实战!xhJaver 居然用线程池优化了。。。有提过怎么写,感兴趣的读者能够回去看一下,不过我在这里再写一下,话不多说来看下代码

并发代码

倡议用 PC 端查看,所有代码都可间接复制运行,代码中重要的点都有具体正文

  1. 首先,咱们先定义这三个接口
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, 那么就及时中断其余两个线程

灵光乍现

上一次的代码曾经实现了多线程执行工作,可是这线程间通信怎么办呢?怎么能力依据一个线程的执行后果而打断其余线程呢?我想到了以下几点

  1. 共享变量 public static volatile boolean end = true;

    • 这个共享变量就代表是否完结三个线程的执行 如果为 true 的话,代表完结,false 的话代表不完结线程执行
  2. 计数器 public static AtomicInteger count =new AtomicInteger(0);

    • 每当每个线程执行完的话,如果返回 true, 计数器就 +1,当计数器变为 3 的时候,就代表这个人征信没问题
  3. 中断办法 interrupt()

    • 咱们会独自开个线程始终循环检测这个变量,当检测到为 true 的时候,就会调用中断办法中断这三个线程
  4. 阻塞线程 countDownLatch

    • 咱们程序往下执行须要获取后果,获取不到这个后果的话,就要始终等着。咱们能够用这个线程阻塞的工具,一开始给他设置数量为 1,当满足持续向下执行的条件时,调用 countDownLatch.countDown();,在主线程那里countDownLatch.await(); 一下这样当检测到数量为 0 的时候,主线程那里就持续往下执行了,话不多说,来看代码

代码优化

倡议用 PC 端查看,所有代码都可间接复制运行,代码中重要的点都有具体正文

  1. 首先,还是创立接口
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 能够靠索引,而我只能靠打工,加油,打工人!
退出移动版