preface

In actual work scenarios, there will be many such requirements: large screen classification statistics, home page display of different information, XXX page aggregation display

These requirements often exist in different tables or other data structures. If the requirements for aggregated information are divided into different interfaces for query, it is inevitable that the efficiency will be lower

After all, it is necessary to call multiple background interfaces to collect different data for classified display, so there are many countermeasures at present

For example, client cache, server memory cache, Redis cache, database cache, front-end page static and so on

But in addition to caching, can all the data be queried in one interface and returned together to the front page? How do you guarantee the performance of this interface?

You can try multithreading to improve interface performance by splitting a large task into smaller tasks and distributing them to each thread for execution

To get the results of each thread, we can use the Callable interface

Callable

The following will simulate the statistics interface of the large screen chart on the home page: the way of water fee, electricity fee and rent

/** * <p> * Graph statistics * </p> **@authorThousand hands shura *@date 2020/12/25 0025 17:42
 */
public class CallableDemo {

    public static void main(String[] args) throws InterruptedException {
        long beginTime = System.currentTimeMillis();

        gatherStatistics();

        long endTime = System.currentTimeMillis();
        System.out.println("Home screen statistics interface time :" + (endTime - beginTime) + "毫秒");
    }

    /** ** *@returnStatistical results */
    public static Map<String, List<BigDecimal>> gatherStatistics() throws InterruptedException {
        Map<String, List<BigDecimal>> map = new HashMap<>(6);
        map.put("Statistical water charges", statisticsWaterCost());
        map.put("Statistical electricity charges", statisticsElectricityCost());
        map.put("Statistical Rent", statisticsResideCost());
        return map;
    }

    /** * Count water charges **@returnList of water charges */
    public static List<BigDecimal> statisticsWaterCost(a) throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
        List<BigDecimal> vos = new ArrayList<>();
        for (int i = 1; i <= 100; i++) {
            vos.add(BigDecimal.valueOf(i));
        }
        return vos;
    }

    /** ** Statistics of electricity charges **@returnElectricity statistics */
    public static List<BigDecimal> statisticsElectricityCost(a) throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        List<BigDecimal> vos = new ArrayList<>();
        for (int i = 100; i <= 200; i++) {
            vos.add(BigDecimal.valueOf(i));
        }
        return vos;
    }

    /** ** rent statistics **@returnRental statistics */
    public static List<BigDecimal> statisticsResideCost(a) throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        List<BigDecimal> vos = new ArrayList<>();
        for (int i = 200; i <= 300; i++) {
            vos.add(BigDecimal.valueOf(i));
        }
        returnvos; }}Copy the code

The total time was 6003 ms, followed by a wave of modification through multi-threading

/** * <p> * Graph statistics * </p> **@authorThousand hands shura *@date 2020/12/25 0025 17:42
 */
public class CallableDemo {

    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long beginTime = System.currentTimeMillis();

        gatherStatistics();

        long endTime = System.currentTimeMillis();
        System.out.println("Home screen statistics interface time :" + (endTime - beginTime) + "毫秒");
    }

    /** ** *@returnStatistical results */
    public static Map<String, List<BigDecimal>> gatherStatistics() throws InterruptedException, ExecutionException {
        Future<List<BigDecimal>> statisticsWaterCostFuture = executorService.submit(() -> statisticsWaterCost());
        Future<List<BigDecimal>> statisticsElectricityCostFuture = executorService.submit(() -> statisticsElectricityCost());
        Future<List<BigDecimal>> statisticsResideCostFuture = executorService.submit(() -> statisticsResideCost());

        Map<String, List<BigDecimal>> map = new HashMap<>(6);
        map.put("Statistical water charges", statisticsWaterCostFuture.get());
        map.put("Statistical electricity charges", statisticsElectricityCostFuture.get());
        map.put("Statistical Rent", statisticsResideCostFuture.get());
        return map;
    }

    /** * Count water charges **@returnList of water charges */
    public static List<BigDecimal> statisticsWaterCost(a) throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
        List<BigDecimal> vos = new ArrayList<>();
        for (int i = 1; i <= 100; i++) {
            vos.add(BigDecimal.valueOf(i));
        }
        return vos;
    }

    /** ** Statistics of electricity charges **@returnElectricity statistics */
    public static List<BigDecimal> statisticsElectricityCost(a) throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        List<BigDecimal> vos = new ArrayList<>();
        for (int i = 100; i <= 200; i++) {
            vos.add(BigDecimal.valueOf(i));
        }
        return vos;
    }

    /** ** rent statistics **@returnRental statistics */
    public static List<BigDecimal> statisticsResideCost(a) throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        List<BigDecimal> vos = new ArrayList<>();
        for (int i = 200; i <= 300; i++) {
            vos.add(BigDecimal.valueOf(i));
        }
        returnvos; }}Copy the code

When the home page large screen statistics interface is transformed into multithreading, the execution time is 3002 ms. That’s an increase of 3,000 milliseconds

It was just a small change, adding three things: thread pools, Callable, and Future

FutureTask

You can see from the code above that the Future can get the return value of a Callable. Let’s look directly at the thread pool submit method

use

So we can instantiate the FutureTask ourselves and pass in the Callable task and let the thread execute it to get the return value

/** * <p> * Graph statistics * </p> **@authorThousand hands shura *@date 2020/12/25 0025 17:42
 */
public class CallableDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(() -> "123456");
        newThread(futureTask).start(); System.out.println(futureTask.get()); }}Copy the code

Well! You can still get the return value of a Callable, but what is the underlying implementation mechanism?

handwritten

The get method blocks until the run method is executed, so let’s implement it

/** * <p> * Write FutureTask * </p> **@authorThousand hands shura *@date2020/12/25 0025 that is * /
public class MyFutureTask<T> implements Runnable {

    private Callable<T> callable;

    private volatile T result;

    private volatile boolean isExecuteRun = false;

    private BlockingDeque<Thread> waiters = new LinkedBlockingDeque<>(1000);


    public MyFutureTask(Callable<T> callable) {
        this.callable = callable;
    }

    @Override
    public void run(a) {
        try {
            result = callable.call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            isExecuteRun = true;
        }
        Thread thread;
        while((thread = waiters.poll()) ! =null) { LockSupport.unpark(thread); }}public T get(a) throws InterruptedException, ExecutionException {
        while(! isExecuteRun) { Thread thread = Thread.currentThread(); waiters.offer(thread); LockSupport.park(thread); }returnresult; }}Copy the code
/** * <p> * Graph statistics * </p> **@authorThousand hands shura *@date 2020/12/25 0025 17:42
 */
public class CallableDemo {


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyFutureTask<String> futureTask = new MyFutureTask<>(() -> "123456");
        newThread(futureTask).start(); System.out.println(futureTask.get()); }}Copy the code

How easy it is to find out from the results that you can implement the idea of FutureTask!

Request to merge

There is also a common implementation class for the Future interface, CompletableFuture, which can be used to optimize a query interface in practice

/** ** <p> * Request merge * </p> **@authorThousand hands shura *@date 2020/12/25 0025 17:42
 */
public class CallableDemo {

    private static BlockingDeque<GetByIdRequest> waiters = new LinkedBlockingDeque<>(1000);

    private static List<User> users = new ArrayList<>();

    static {
        for (int i = 1; i <= 5; i++) {
            String id = String.valueOf(i);
            users.add(new User(id, "Zhang" + id));
        }


        // A scheduled task that processes requests in the queue every second
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleWithFixedDelay(() -> {

            // Collect the request list
            List<GetByIdRequest> getByIdRequests = new ArrayList<>();
            int size = waiters.size();
            for (int i = 0; i < size; i++) {
                getByIdRequests.add(waiters.poll());
            }

            // Group requests by request ID so that the database is queried only once for the same ID. And notifies the waiting thread of the query result set
            Map<String, List<GetByIdRequest>> collect = getByIdRequests.stream().collect(Collectors.groupingBy(GetByIdRequest::getId));
            collect.forEach((id, list) -> {
                try {
                    User user = getById(id);
                    list.forEach(v -> v.getCompletableFuture().complete(user));
                } catch(InterruptedException e) { e.printStackTrace(); }}); },1.1, TimeUnit.SECONDS);
    }

    /** * Request class to query users by ID */
    static class GetByIdRequest {
        private String id;
        private CompletableFuture<User> completableFuture;

        public GetByIdRequest(String id) {
            this.id = id;
            this.completableFuture = new CompletableFuture<>();
        }

        public String getId(a) {
            return id;
        }

        public CompletableFuture<User> getCompletableFuture(a) {
            returncompletableFuture; }}/** * User entity class */
    static class User {
        private String id;
        private String name;

        public User(String id, String name) {
            this.id = id;
            this.name = name;
        }

        public String getId(a) {
            return id;
        }

        public String getName(a) {
            returnname; }}public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                try {
                    User user = BlockGetById("1");
                    System.out.println(Thread.currentThread().getName() + "End of execution, obtained user information" + user + ", the execution end time is: + System.currentTimeMillis());
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch(InterruptedException e) { e.printStackTrace(); }}); } executorService.shutdown(); }public static User BlockGetById(String id) throws ExecutionException, InterruptedException {
        GetByIdRequest getByIdRequest = new GetByIdRequest(id);
        waiters.offer(getByIdRequest);
        // block to wait for results
        return getByIdRequest.completableFuture.get();
    }

    /** * query name ** based on primary key ID@paramId Primary key ID *@returnName * /
    public static User getById(String id) throws InterruptedException {
        // Simulate database query operation, sleep for one second
        TimeUnit.SECONDS.sleep(1);
        Map<String, List<User>> userIdMap = users.stream().collect(Collectors.groupingBy(User::getId));
        List<User> users = userIdMap.get(id);
        returnusers ! =null && users.size() > 0 ? users.get(0) : null; }}Copy the code

To reduce the frequency of database queries, multiple requests can be combined and the query results notified to the corresponding requests

This is also a way of dealing with concurrency, but the disadvantage is that the query is no longer a direct query, but a waiting notification

It slows down the response time on a per-request basis but improves query efficiency on an overall basis

conclusion

When the same request handles a lot of business and the tasks are not related to each other! FutureTask can be used to optimize application performance!

MyFutureTask by custom writing I believe you have a certain understanding of the underlying implementation principle of FutureTask, nothing more than the use of blocking/wake up

I also share tips on how CompletableFuture implements request merges to reduce database stress