background

Recently, during the investigation of production environment problems, it was found that the interface for commodity details reported RPC call timeout from time to time. After checking the code, it was found that the query activities in the interface took a long time and were executed in serial. After careful examination, it was found that the query activities could be executed in parallel to shorten the interface query time. For example, our product details interface needs to display active labels such as vertical reduction, ladder full reduction and group purchase. You need to query the activity information three times and then assemble the activity label information. If each query takes 1s and is called in serial mode, the whole interface will need at least 3s, which is unacceptable for us. In fact, the JDK provides several very convenient ways to execute tasks in parallel.

  • CountDownLatch

  • ExecutorService.invokeAll()

  • Fork/Join divide and conquer is a bit of a shadow of MapReduce, if you are interested


The improved scheme

  • Code examples:
    private void assemblyActivityTag(CartItemDTO itemDTO){
            
        //1. Query the activity information, which takes 1s
         
        //2. Query the full and decrement activity of the ladder, which takes 1s
        
        //3. Query the group purchase information, which takes 1s
        
        //4. Assemble the active label information, which takes 1s
        
        // Serial execution takes 4s
    }
Copy the code
  • CountDownLatch
     private void assemblyActivityTag(CartItemDTO itemDTO){
        ExecutorService executorService = Executors.newCachedThreadPool();
        CountDownLatch latch = new CountDownLatch(3);
        executorService.execute(new Runnable() {
            @Override
            public void run(a) {
            //1latch.countDown(); }}); executorService.execute(new Runnable() {
            @Override
            public void run(a) {
                //2latch.countDown(); }}); executorService.execute(new Runnable() {
            @Override
            public void run(a) {
                //3. Query group purchase informationlatch.countDown(); }});try {
            // Remember to add a timeout to prevent blocking the main thread
            latch.await(3000,TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //4. Wait for all subtasks to complete and assemble the active label information
         
        // close the thread pool
        executorService.shutdown();
    }
Copy the code
  • ExecutorService.invokeAll()
private void assemblyActivityTag(CartItemDTO itemDTO) {

        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Callable<String>> tasks = Lists.newArrayList();
        tasks.add(new Callable<String>() {
            @Override
            public String call(a) throws Exception {
                //1
                return null; }}); tasks.add(new Callable<String>() {
            @Override
            public String call(a) throws Exception {
                //2
                return null; }}); tasks.add(new Callable<String>() {
            @Override
            public String call(a) throws Exception {
                //3. Query group purchase information
                return null; }});try {
            List<Future<String>> futureList = executorService.invokeAll(tasks, 3000, TimeUnit.MILLISECONDS);
            for (Future<String> future : futureList) {
                // Get the thread execution result
                try {
                    String activityTag = future.get();
                } catch(ExecutionException e) { e.printStackTrace(); }}}catch (InterruptedException e) {
            e.printStackTrace();
        }

        //4. Assemble the active label information

        // close the thread pool
        executorService.shutdown();
    }
Copy the code

Pay attention to points and differences

  • When using CountDownLatch, use a thread-safe container to handle child thread returns as much as possible to avoid dirty data in multithreaded situations.
  • If you want to know every child thread corresponding return values, the ExecutorService. InvokeAll () method, is can’t distinguish between, can only rely on the order of the return value to match.
  • When using the preceding two methods, set the timeout period to prevent mainline tasks from being blocked due to the long execution time of subtasks
  • When the thread pool runs out, remember shutdown()

conclusion

In view of the author’s talent and learning, the shortcomings of the article also hope you do not misgive correct, if there is the same purely coincidence