Talking about concurrent programming: Future model (Java, Clojure, Scala multilingual perspective)

Posted by theDog on Sat, 06 Jul 2019 19:23:12 +0200

0x00 Preface

In fact, the Future model is not far away from us. If you have been exposed to such excellent open source projects as Spark and Hadoop, pay attention to their output logs when you run the program. If you are not careful, you will find Future.

There are many excellent design patterns in the field of concurrent programming, such as the common Producer-Consumer pattern, Pipeline pattern and Future pattern. These patterns have their own applicable scenarios and can effectively solve concurrent problems.

This article will focus on sharing some knowledge points related to the Future model.

Article structure

The structure of this paper is as follows:

  • Let's first explain what a Future model is.
  • Java is inevitably one of the most popular languages, so we will use Java to implement a Future scenario ourselves.
  • Since Java already provides support for Future in the concurrent package, here's an example of using the concurrent package.
  • In addition to Java, many languages have provided support for the Future model at the language level. In this part, we use different languages to demonstrate the Future model.

Introduction of 0x01 Future Model

What is Future Model? We can roughly understand that the Future model is a combination of asynchronous requests and proxy patterns.

For ease of understanding, let's give a scenario to illustrate. Or suppose we are an e-commerce platform where users place orders on our website.

As shown below, the user operates on the client, which sends data to the Future server, which retrieves the complete order data from the back-end data interface and responds to the user. Let's simulate the behavior of user orders.

  1. When the user finishes picking up the goods and starts placing the order, the client sends the request 1 to the server.
  2. According to the client's information, the server obtains the complete order data from the background. Here is a description, for example, the user client sends only a few commodity IDs and quantities, our server needs to read various information from the background database, such as merchants, commodities, orders, inventory and so on, and finally assemble a complete order to return.
  3. Step 2 is time-consuming, so the server directly returns a forged data, such as an order id, to the client.
  4. After the client receives the order id, it begins to check the order information, such as whether the quantity of goods is correct. Note: If you need to pay, you have to wait for the return of the final order data, that is, the return of the real data. If the data is not returned, wait until it returns.
  5. At this time, the complete order information stitching is completed, the complete data of the order is returned, and the user pays and completes the order.

0x02 implements one by itself

In this section, we implement a Future model in Java code.

Code structure

As shown in the figure, the code is divided into the following parts:


  • IData interface defines a data interface, which is implemented by FutureData and RealData.

  • FutureData is the packaging of RealData and a proxy of dui real data, encapsulating the waiting process of obtaining real data. They all implement a common interface, so there is no difference for the client program group.

  • The Client class sends a request for data to the Server, Sever returns a Future to the Client first, and the Client receives the data and then proceeds to perform other operations.

  • When RealData's data is complete, it will be returned to Client. The return operation here is getResult() of FutureData.

  • Because the notifyAll and wait functions in FutureData, the main program will wait for the assembly to complete before continuing the main process, that is, if no assembly is completed, the main function will wait all the time.

Here is only a brief introduction, which will be explained in detail in the code.

Be careful:

In the method invoked by the client, a single thread is enabled to organize the real data, which is closed to the main function of the calling client.
. 

1. Code List IData Interface

Both FutureData and RealData inherit from the IData interface.

/**
 * Data Interface Class
 * Created by Dante on 2017/4/8.
 */
public interface IData {
    public String getResult();
}

2. Code List FutureData Class

FutureData is a data class returned directly to the client through Server. It can be understood here that FutureData is an encapsulation of real data.

/**
 * Created by Dante on 2017/4/8.
 */
/*
 * A wrapper that returns RealData quickly is implemented, but it is not a real return result.
 */
public class FutureData  implements IData {
    protected RealData realData=null; //FutureData is a packaging of RealData
    protected boolean isReady=false;
    public synchronized void setRealData(RealData realData)
    {
        if(isReady)
        {
            return;
        }
        this.realData=realData;
        isReady=true;
        notifyAll(); //When the set method of the Future wrapper class is called, the thread RealData is awakened, the same getResult() method
    }

    @Override
    public synchronized String getResult() { //Waiting for RealData construction to complete
        while(!isReady)
        {
            try{
                wait(); //Wait until RealData is injected
            }catch (Exception e)
            {}
        }
        return realData.result; //Realization of RealData
    }
}

3. Code List RealData Class

RealData is the ultimate real data, and we can understand that the construction process of RealData takes a lot of time.

/**
 * The real data class, which is the data returned to the user, generates the data very slowly.
 * Created by Dante on 2017/4/8.
 */
public class RealData  implements IData {
    protected  final String result;
    public RealData(String para)
    {
        StringBuffer sb=new StringBuffer();
        //Simulate a very slow tectonic process
        for(int i=0;i<100;i++)
        {
            sb.append(para);
            try {
                Thread.sleep(100);//Instead of a very slow process
            } catch (Exception e) {

            }
        }
        result=sb.toString();
    }
    public String getResult()
    {
        return result;
    }
}

4. Code List Server Class

Server side, responsible for receiving data requests from Client, constructing data, and returning.

Because RealData is built slowly, it is placed in a separate thread.

Note: A proxy's Future data is returned here, but when the Client calls getResult(), it waits until the actual data construction is completed.

/**
 * Server End, responsible for receiving data requests from Client, constructing data, and returning
 * Created by Dante on 2017/4/8.
 */
public class Server {
    public IData request(final String queryString) {
        final FutureData future = new FutureData();
        new Thread() //RealData is built slowly and is put into a separate thread
        {
            public void run() {
                RealData realData = new RealData(queryString);
                future.setRealData(realData);
                //Call the set method of future to return and wake up RealData threads to construct data directly.
            }
        }.start();
        return future;  //Immediately returned
    }
}

5. Code List Client Class

The comments in the code are more detailed and can be understood by the comments.

/**
 * The main responsibility is to invoke server to initiate the request and use the returned data.
 * Created by Dante on 2017/4/8.
 */
public class Client {
    public static void main(String[] args) {
        System.out.println("Establishment and Server Connection!");
        Server server =new Server();
        //The result is returned immediately, because the result is FutureData, not RealData.
        System.out.println("towards Server Send data requests!");
        IData data=server.request("name");
        System.out.println("Complete the request:" + data.toString() );
        try{
            //Representatives'handling of other business
            //In the process, RealData is sworded, making full use of waiting time.
            //Thread.sleep(2000);
            System.out.println("Start dealing with other businesses!");
        }catch(Exception e)
        {}
        System.out.println("Receive real data:\n"+data.getResult());
    }
}

Future in 0x03 Java concurrent package

Future in the concurrent package is more convenient to use, so I will not introduce it here. The students who are interested in running the code will see the results clearly.

/**
 * Created by Dante on 2017/4/8.
 */
import java.util.concurrent.*;

/**
 * Test Java Future Usage
 */
public class FutureTest {

    public static class Task1 implements Callable<String> {
        @Override
        public String call() throws Exception {
            String tid = String.valueOf(Thread.currentThread().getId());
            System.out.printf("Thread#%s : in call\n", tid);
            Thread.sleep(111);
            return tid;
        }
    }

    public static class Task2 implements Callable<String> {
        @Override
        public String call() throws Exception {
            String tid = String.valueOf(Thread.currentThread().getId());
            System.out.printf("Thread#%s : in call\n", tid);
            Thread.sleep(1111);
            return tid;
        }
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService es = Executors.newCachedThreadPool();

        Future<String> restult1 = es.submit(new Task1());
        Future<String> restult2 = es.submit(new Task2());

        System.out.println(restult1.get());
        System.out.println(restult2.get());
    }

}

Future in 0x04 Scala

In scala, Future is used in two ways:

  • Blocking: In this way, the parent actor or main program stops executing until all future s complete their respective tasks. Use it through scala.concurrent.Await.
  • Non-Blocking, also known as Callback: The parent actor or the main program starts future during execution, and the future task and the parent actor execute in parallel. When each future completes the task, the parent actor is notified. It is used in onComplete, onSuccess, onFailure mode.

This section of Scala refers to Jason Ding's article.

1. Blocking mode

The first example shows how to create a future, and then wait for its results through blocking. Although blocking is not a good use, it can illustrate the problem.

In this example, by calculating 1 + 1 at some time in the future, the results are returned.

  • ExecutionContext.Implicits.global uses the current global context as the implicit context.
  • duration. allows us to use 1 second, 200 milli meter interval literals.
  • Await.result waits for the Future task to complete in a blocking manner, and throws a TimeoutException exception if the Future timeout is not completed.
/**
  * Created by Dante on 2017/4/23.
  */
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

object FutureTest{

  def main(args: Array[String]) {
    val f = Future {
      println("Working on future task!")
      Thread.sleep(1000)
      1+1
    }

    println("Waiting for future task complete!")
    // If Future does not return within the time specified by Await,
    // java.util.concurrent.TimeoutException will be thrown
    val result = Await.result(f, 1 second)
    println("The future task result is " + result)
  }
}

2. Non-blocking mode (callback mode)

Sometimes you just need to listen to Future's completion events and respond to them, not to create a new Future, but just to create side effects.
Future tasks are executed asynchronously through three callback functions: onComplete, onSuccess and onFailure, which are only exceptions to the first one.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Random, Success}
/**
  * Created by Dante on 2017/4/23.
  */
object NonBlockingFutureTest {
  def main(args: Array[String]) {

    println("starting calculation ...")
    val f = Future {
      Thread.sleep(Random.nextInt(500))
      42
    }

    println("before onComplete")
    f.onComplete{
      case Success(value) => println(s"Got the callback, meaning = $value")
      case Failure(e) => e.printStackTrace
    }
    // do the rest of your work
    println("A ...")
    Thread.sleep(100)
    println("B ....")
    Thread.sleep(100)
    println("C ....")
    Thread.sleep(100)
    println("D ....")
    Thread.sleep(100)
    println("E ....")
    Thread.sleep(100)
    Thread.sleep(2000)
  }
}

Future in 0x05 Clojure

Clojure is a very interesting language. The grammar looks more disgusting than Scala, but it feels good to adapt, and it's easier to understand functional programming through Clojure.

Because Clojure is not very deep, just a little fun to learn, the Future model is less used, in order to make a horizontal comparison, here is only a small example for learning.

  • Clojure supports future directly at the grammatical level, using the key word "future".
  • deref or @ can be used to dereference a future object.
;; Clojure Direct support at the grammatical level futureļ¼ŒUse future Keyword is enough
user=> (def f (future (Thread/sleep 10000) (println "done") 100))
#'user/f
;;if you wait 10 seconds before dereferencing it you'll see "done"
;; When you dereference it you will block until the result is available.
user=> @f
done
100

Summary of 0xFF

My own level of concurrency is also half-suspended. Blogging is mainly a learning process. It took three weekends to complete this blog before and after. Although the process is a bit painful, the harvest is still great.

Many people's blogs, including the contents of books, are listed in the following sections.
When writing a blog, my own ideas, even though the content is mosaic and sorted out, but after all, the ideas are my own, and the organizational structure of the article has been considered for a long time. In order to understand the future, I also refer to several programming languages, including lo, which is a very minority language, but I did not write it in the end.

Quote

Authors: dantezhao |Brief book | CSDN | GITHUB

Personal Home Page: http://dantezhao.com
Articles can be reproduced, but they must be hyperlinked to indicate the original source of the article and the author's information.

Topics: Scala Java Programming Spark