If a project is always running on a single thread, it will inevitably encounter some performance issues, so we should try to use multithreading in moderation (while keeping it thread-safe) in our redevelopment.

Contents of this tutorial:

  1. Simulate a single-threaded scenario
  2. Concurrent programming with Callable
  3. Asynchronous processing is implemented with DeferedResult

Simulate a single-threaded scenario

/** * Created by fant.j. */ @restController@slf4j Public Class AsyncController {/** * single-thread test * @return
     * @throws InterruptedException
     */
    @RequestMapping("/order")
    public String order() throws InterruptedException {
        log.info("Main thread started");
        Thread.sleep(1000);
        log.info("Main thread return");
        return "success"; }}Copy the code

We think of a thread resting for one second as simulating the time it takes to process business. Obviously, this is a single thread.

nio-8080-exec-1

Concurrent programming with Callable

/** * asynchronous * @ with Callablereturn
     * @throws InterruptedException
     */
    @RequestMapping("/orderAsync")
    public Callable orderAsync() throws InterruptedException {
        log.info("Main thread started");
        Callable result = new Callable() {
            @Override
            public Object call() throws Exception {
                log.info("Secondary thread started");
                Thread.sleep(1000);
                log.info("Secondary thread return");
                return "success"; }}; log.info("Main thread return");
        return result;
    }

Copy the code

As we can see, the start and return (end processing) of the main thread is performed first, and then the secondary thread performs the real business processing. The main thread is used to call (wake up) the child thread, which returns an Object and then returns it to the user.

This allows concurrent processing, but one problem is that the main thread and the sub-thread are not completely separated, after all, it is a nested sub-thread.

So to optimize our implementation, I simulate messaging middleware here to achieve complete separation of main thread sub-threads.

Asynchronous processing is implemented with DeferedResult

Since this chapter focuses on the principles of concurrent programming, instead of using a ready-made message queue, we simulate a message queue.

MockQueue .java
/** * Created by fant.j. */ @component @slf4j public class MockQueue {// Private String placeOrder; // Order Completion message private String completeOrder; public StringgetPlaceOrder() {
        return placeOrder;
    }

    public void setPlaceOrder(String placeOrder) throws InterruptedException {
        new Thread(()->{
            log.info("Received an order request"+placeOrder); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // assign completeOrder this.pleteOrder = placeOrder; log.info("Order request processed."+placeOrder);
        }).start();
    }

    public String getCompleteOrder() {
        return completeOrder;
    }

    public void setCompleteOrder(String completeOrder) { this.completeOrder = completeOrder; }}Copy the code

Note that in the setPlaceOrder(String placeOrder) method, I created a new thread to handle the order. The parameter passed in is an order number, and after 1s of successful processing, the order number is passed to the completeOrder field, indicating that the user placed the order successfully. I’ll give the controller the code below to call this method

@autoWired private MockQueue MockQueue; @Autowired private DeferredResultHolder deferredResultHolder; . @RequestMapping("/orderMockQueue")
    public DeferredResult orderQueue() throws InterruptedException {
        log.info("Main thread started"); / / 8 digit randomly generated String orderNumber. = RandomStringUtils randomNumeric (8); mockQueue.setPlaceOrder(orderNumber); DeferredResult result = new DeferredResult(); deferredResultHolder.getMap().put(orderNumber,result); Thread.sleep(1000); log.info("Main thread return");

        return result;
    }
Copy the code

Ok, then we also need a mediation class to hold the order number and process the result. The reason why we need such a class is because we mentioned earlier that we want to separate the main thread from the secondary thread, so we need a mediation to hold the processing information (such as the order number information, and the processing result information), and we can determine whether the processing result is empty to know whether the secondary thread has executed. So let’s write a mediation class DeferredResultHolder.

######DeferredResultHolder .java

/** * Created by Fant.j. */ @Component public class DeferredResultHolder {/** * String: */ Private Map<String,DeferredResult> Map = new HashMap<>(); public Map<String, DeferredResult>getMap() {
        return map;
    }

    public void setMap(Map<String, DeferredResult> map) { this.map = map; }}Copy the code

Again, why do we need a class like this? Because we said that to separate the main thread from the secondary thread, we need a mediation to hold the processing information (for example, the order number information, and the processing result information), each order must correspond to a result. Otherwise it would be a mess.

DeferredResult is the object to put the results of the processing.

We need to write a listener that listens once in 100 milliseconds to see if the completeOrder in the MockQueue class has a value. If so, the order needs to be processed. Let’s write a listener.

QueueListener .java
/** * Queue listener * Created by fant.j. */ @component @slf4j public class QueueListener implements ApplicationListener<ContextRefreshedEvent>{ @Autowired private MockQueue mockQueue; @Autowired private DeferredResultHolder deferredResultHolder; @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { new Thread(()->{while(true){// Determine if the CompleteOrder field is emptyif (StringUtils.isNotBlank(mockQueue.getCompleteOrder())){

                    String orderNumber = mockQueue.getCompleteOrder();

                    deferredResultHolder.getMap().get(orderNumber).setResult("place order success");

                    log.info("Return order processing result"); / / set is empty CompleteOrder, said treatment success mockQueue. SetCompleteOrder (null); }else{ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }}Copy the code



After the line, I’ll bring you a batch of dry goods, a custom thread pool at https://www.jianshu.com/p/832f2b162450

After learning this, look at the following.

In the previous code, there were two sections that used new threads () to create threads. Once we have our own Thread pool, we can use the Thread pool to assign tasks to threads. In the custom Thread section, I used the second configuration method (@async annotation for threads). Modified as follows:

    @Async
    public void setPlaceOrder(String placeOrder) throws InterruptedException {
            log.info("Received an order request"+placeOrder); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // assign completeOrder this.pleteOrder = placeOrder; log.info("Order request processed."+placeOrder);
    }
Copy the code

Let’s look at the effect:

The ones circled in red are the threads allocated from our own thread pool.

Thank you!

Here are all my essays:

Popular frameworks

SpringCloud

springboot

nginx

redis

Underlying implementation principle:

Java NIO tutorial

Java Reflection details

Java concurrent learning notes

Java Servlet tutorial

JDBC component details

Java NIO tutorial

Java language/versioning studies