Multithreading has to talk about the Future class

Posted by Chinese on Fri, 18 Feb 2022 04:29:02 +0100

Link: link.

In high-performance programming, concurrent programming has become a very important part. When the performance of single core CPU has reached the limit, we can only further improve the performance of the system through multi-core, so concurrent programming is born.

Because concurrent programming is more difficult and error prone than serial programming, we need to learn from some previous excellent and mature design patterns to make our design more robust and perfect.

Future model in life

In order to understand the Future model more quickly, let's take a look at an example in life.

Scenario 1:

It's lunchtime. The students are going to have dinner. Xiao Wang went downstairs and walked for 20 minutes. He came to KFC, ordered and lined up. It took a total of 20 minutes to eat and another 20 minutes to walk back to the company to continue working, for a total of 1 hour.

Scenario 2:

It's lunchtime. The students are going to have dinner. Xiao Wang ordered a KFC takeout. Soon, he got an order (although the order can't be eaten, he's afraid he can't eat with the order). Then Xiao Wang can continue to work. After 30 minutes, the takeout arrived. Then Xiao Wang spent 10 minutes eating. Then he can continue to work. He successfully rolled it to Xiao Wang next door.

Obviously, in these two scenarios, Xiao Wang's working hours are more compact, especially those waiting in line can let the takeout do it, so he can focus more on his own work. Smart you should have realized that scenario 1 is a typical synchronous function call, while scenario 2 is a typical asynchronous call.

Another feature of the asynchronous call in scenario 2 is that it has a return value, which is our order. This order is very important. With this order, we can get the result corresponding to the current call.

The order here is like the Future in the Future model. It is a contract and a commitment. Although the order cannot be eaten, holding the order is not afraid of not eating. Although Future is not the result we want, we can get the result we want in the Future with Future.

Therefore, the Future pattern well solves the asynchronous calls that need to return values.

Key roles in the Future model

A typical Future model consists of the following parts:

  • Main: when the system starts, call the Client to send a request
  • Client: return the Data object, return FutureData immediately, and start the ClientThread thread to assemble RealData
  • Data: interface to return data
  • FutureData: Future data is constructed quickly, but it is a virtual data. It needs to be assembled with RealData, like an order
  • RealData: the construction of real data is relatively slow, such as KFC lunch in the above example.

The relationship between them is shown in the figure below:

Among them, it is worth noting that Data, RealData and FutureData. This is a group of typical proxy modes. The Data interface represents external Data and RealData represents real Data, just like lunch. It costs a lot of time to obtain it; FutureData, as the agent of RealData, is similar to an order / contract. Through FutureData, RealData can be obtained in the future.

Therefore, the Future model is essentially a practical application of the agent model.

Implement a simple Future pattern

According to the above design, let's implement a simple agent mode!

The first is the Data interface, which represents Data:

public interface Data {
    public String getResult ();
}

Next comes FutureData, which is also the core of the whole Future model:

public class FutureData implements Data {
    // RealData needs to be maintained internally
    protected RealData realdata = null;          
    protected boolean isReady = false;
    public synchronized void setRealData(RealData realdata) {
        if (isReady) { 
            return;
        }
        this.realdata = realdata;
        isReady = true;
        //RealData has been injected. Notify getResult()
        notifyAll();                               
    }
    //Will wait for RealData construction to complete
    public synchronized String getResult() {         
        while (!isReady) {
            try {
                //Wait until RealData is injected
                wait();                           
            } catch (InterruptedException e) {
            }
        }
        //The data you really need is obtained from RealData
        return realdata.result;                      
    }
}

Here is RealData:

public class RealData implements Data {
    protected final String result;
    public RealData(String para) {
        StringBuffer sb=new StringBuffer();
        //Assuming that it is very slow here, it is not easy to construct RealData
        result =sb.toString();
    }
    public String getResult() {
        return result;
    }
}

Then get the Data from the Client:

public class Client {
    //This is an asynchronous method, and the returned Data interface is a Future
    public Data request(final String queryStr) {
        final FutureData future = new FutureData();
        new Thread() {                                      
            public void run() {                     
                // The construction of RealData is slow, so it is carried out in a separate thread
                RealData realdata = new RealData(queryStr);
                //When setRealData(), it will notify() the objects waiting on this future
                future.setRealData(realdata);
            }                                               
        }.start();
        // FutureData will be returned immediately without waiting for RealData to be constructed
        return future;                          
    }
}

The last Main function concatenates everything:

public static void main(String[] args) {
    Client client = new Client();
    //This will be returned immediately because you get FutureData instead of RealData
    Data data = client.request("name");
    System.out.println("Request complete");
    try {
        //Here, you can use a sleep instead of processing other business logic
        //In the process of processing these business logic, RealData is created to make full use of the waiting time
        Thread.sleep(2000);
    } catch (InterruptedException e) {
    }
    //Use real data. If the data is not ready here, getResult() will wait for the data to be ready before returning
    System.out.println("data = " + data.getResult());
}

This is the simplest implementation of the Future pattern. Although it is simple, it already contains the most essential part of the Future pattern. It plays a very important role in understanding the Future object inside JDK.

Topics: Java Programming Design Pattern Multithreading future