Callable/Future interface of concurrent programming (taking word best matching algorithm as an example)

table of Contents

One, recognize the Callable interface

Second, recognize the Future interface

3. Example: the best word match

1. Realization of the shortest edit distance algorithm

2. Store the best matching results

3. Load the dictionary

Fourth, the serial version

Five, concurrent version

1. BestMatchingBasicTask class

2. BestMatchingBasicConcurrentCalculation class

3. BestMatchingConcurrentMain class

Six, verification results

1. Serial version results

2. Concurrent version results


One, recognize the Callable interface

The knowledge of Java multithreading is very rich. Before that, I only understood the execution tasks of the Runnable interface. After further study, I found that there is actually another multithreaded task interface with similar functions in Java, which is the Callable interface. .

The difference between Runnable and Callable interfaces is that the latter provides a return value after implementing the call method of the interface. The interface method difference is as follows:

Runnable interface

public class A implements Runnable{
    /**
     * @param 
     * @return void
     * @author Charzous
     * @date 2021/1/28 14:59
     *
     */
    @Override
    public void run() {

    }
}

Callable interface 

public class A implements Callable {
    /**
     * @param 
     * @return java.lang.Object
     * @author Charzous
     * @date 2021/1/28 15:00
     *
     */
    @Override
    public Object call() throws Exception {
        return null;
    }
}

This learning record will introduce the specifics and applications of the Callable interface.

Main features of Callable interface:

  1.  It is a general interface, and the return type of the call method is the same as the type parameter.
  2. The call method is declared, which will be executed when the executor executes the task.
  3. Any kind of verification exception can be thrown, and it should be considered a higher-level application.

Second, recognize the Future interface

The Future interface and the Callable interface appear in an execution task at the same time, and the processing result of the Callable interface must be packaged with the Future interface. The Future interface describes the result of asynchronous calculation.

When we send a Callable task to the executor, it will return an implementation of the Future interface, which can be used to control the execution and task status of the task and obtain the result.

Main features of Future interface:

  1. You can use the cancel method to cancel the execution of the task.
  2. Check the status of the task: whether the isCancelled method is cancelled, and whether the isDone() method ends.
  3. Use the get() method to get the task return value.

3. Example: the best word match

Here, starting from a simple case, learn the implementation process of the Callable interface and the Future interface in practical applications.

The best word matching algorithm can also be said to be a specific application of the shortest edit distance problem . The goal is to calculate the minimum distance between two strings. In short, it turns the first string into the second character. The minimum number of modified characters of the string.

In the word matching task, it is to find the most similar word in the dictionary for a given string word.

Preparations include:

  1. English Dictionary: UKACD , the British Advanced Difficulty Dictionary , which contains more than 250,000 words and idioms, specially used for crossword puzzles.
  2. Similarity measure between words: Lenvenshtein distance refers to the minimum number of insertion, deletion or replacement operations required to convert the first string into the second string. That is, the shortest edit distance mentioned above, the algorithm is implemented using dynamic programming .

Next is the algorithm for implementing the best word matching case. There are serial and concurrent versions to compare the efficiency of the algorithm. The common classes of the two algorithms include the realization of the shortest edit distance algorithm, storage of the results of the best matching algorithm, and dictionary loading.

1. Realization of the shortest edit distance algorithm

The algorithm uses dynamic programming to complete the filling of the m*n two-dimensional array, and finally obtains the value corresponding to the shortest distance stored in a[m][n].

public class LevenshteinDistance {
    /**
     * @param string1
     * @param string2
     * @return int
     * @author Charzous
     * @date 2021/1/28 11:10
     */
    public static int calculate(String string1, String string2) {
        int[][] distances = new int[string1.length() + 1][string2.length() + 1];
        for (int i = 1; i <= string1.length(); i++)
            distances[i][0] = i;

        for (int j = 1; j <= string2.length(); j++)
            distances[0][j] = j;
        //动态规划填表
        for (int i = 1; i <= string1.length(); i++) {
            for (int j = 1; j <= string2.length(); j++) {
                if (string1.charAt(i - 1) == string2.charAt(j - 1))
                    distances[i][j] = distances[i - 1][j - 1];
                else
                    distances[i][j] = minimum(distances[i - 1][j], distances[i][j - 1], distances[i - 1][j - 1]) + 1;
            }
        }
//测试
//        for (int i=0;i<=string1.length();i++){
//            for (int j=0;j<=string2.length();j++)
//                System.out.print(distances[i][j]+" ");
//            System.out.println();
//        }
        return distances[string1.length()][string2.length()];
    }

    private static int minimum(int i, int j, int k) {
        return Math.min(i, Math.min(j, k));
    }
/**类内测试
public static void main(String[] args) {
String s1="lonx";
String s2="long";
int d=LevenshteinDistance.calculate(s1,s2);
System.out.println(d);
}
 */
}

2. Store the best matching result

 

import java.util.List;

public class BestMatchingData {
    private int distance;
    private List<String> words;

    public int getDistance() {
        return distance;
    }

    public List<String> getWords() {
        return words;
    }

    public void setDistance(int distance) {
        this.distance = distance;
    }

    public void setWords(List<String> words) {
        this.words = words;
    }
}

 

3. Load the dictionary

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class WordsLoader {
    /**
     * @param path
     * @return java.util.List<java.lang.String>
     * @author Charzous
     * @date 2021/1/28 11:10
     */
    public static List<String> load(String path) {
        Path file = Paths.get(path);
        List<String> data = new ArrayList<String>();
        try (
                InputStream in = Files.newInputStream(file);
                BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
            String line = null;
            while ((line = reader.readLine()) != null)
                data.add(line);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }
}

Fourth, the serial version

First, implement a simple serial version, which will be used to verify the performance of concurrent processing. The idea of ​​implementation is relatively simple, calculate the distance between the given string and each word in the dictionary, and then save the best result in the List.

import java.util.ArrayList;
import java.util.List;

public class BestMatchingSerialCalculation {
    /**
     * @param word
     * @param dictionary
     * @author Charzous
     * @date 2021/1/28 11:19
     */
    public static BestMatchingData getBestMatchingWords(String word, List<String> dictionary) {
        List<String> results = new ArrayList<String>();
        int minDistance = Integer.MAX_VALUE;
        int distance;
        for (String str : dictionary) {
            distance = LevenshteinDistance.calculate(word, str);
            if (distance < minDistance) {
                results.clear();
                minDistance = distance;
                results.add(str);
            } else if (distance == minDistance) {
                results.add(str);
            }

        }
        BestMatchingData result = new BestMatchingData();
        result.setWords(results);
        result.setDistance(minDistance);

        return result;
    }

}

The following implements the executable main class main, loads the dictionary file, and prints the best matching result and related information.

import java.util.Date;
import java.util.List;

public class BestMatchingSerialMain {
    public static void main(String[] args) {
        Date startTime,endTime;
        List<String> dictionary= WordsLoader.load("data/UK Advanced Cryptics Dictionary.txt");
        System.out.println("Dictionary Size:"+dictionary.size());

        String word="stitter";//待匹配单词
        if (args.length==1)
            word=args[0];

        startTime=new Date();
        BestMatchingData result=BestMatchingSerialCalculation.getBestMatchingWords(word,dictionary);
        endTime=new Date();
        List<String> results=result.getWords();
        System.out.println("Word: "+word);
        System.out.println("Minimum distance: "+result.getDistance());
        System.out.println("耗时:"+(endTime.getTime()-startTime.getTime())+" ms");
        System.out.println("List of best matching words: "+results.size());
        results.forEach(System.out::println);
    }
}

 The results will be compared later.

Five, concurrent version

Up to this point, the executor task will be implemented based on the Callable interface, using the return value of the call method to get the best matching word result, including 3 implementation classes, which are introduced as follows:

1. BestMatchingBasicTask class

Here will execute the task that implements the Callable interface in the executor.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

public class BestMatchingBasicTask implements Callable<BestMatchingData> {
    private int startIdx;
    private int endIdx;
    private List<String> dictionary;
    private String word;
    /**
     * @param startIdx
     * @param endIdx
     * @param dictionary
     * @param word
     * @return 
     * @author Charzous
     * @date 2021/1/28 16:24
     *
     */
    public BestMatchingBasicTask(int startIdx, int endIdx, List<String> dictionary, String word) {
        this.startIdx = startIdx;
        this.endIdx = endIdx;
        this.dictionary = dictionary;
        this.word = word;
    }

    @Override
    public BestMatchingData call() throws Exception {
        List<String> results = new ArrayList<String>();
        int minDistance = Integer.MAX_VALUE;
        int distance;
        for (int i = startIdx; i < endIdx; i++) {
            distance = LevenshteinDistance.calculate(word, dictionary.get(i));//计算给出单词与词典中单词的距离
            //保存最佳匹配结果的单词
            if (distance < minDistance) {
                results.clear();
                minDistance = distance;
                results.add(dictionary.get(i));
            } else if (distance == minDistance)
                results.add(dictionary.get(i));
        }
        //将结果保存并返回
        BestMatchingData result = new BestMatchingData();
        result.setWords(results);
        result.setDistance(minDistance);
        return result;
    }
}

2. BestMatchingBasicConcurrentCalculation class

This class creates the executor and the necessary tasks, and sends the tasks to the executor.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

public class BestMatchingBasicConcurrentCalculation {
    /**
     * @param word
     * @param dictionary
     * @author Charzous
     * @date 2021/1/28 16:27
     *
     */
    public static BestMatchingData getBestMatchingWords(String word, List<String> dictionary) throws ExecutionException, InterruptedException {

        int numCores = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numCores);
        int size = dictionary.size();
        int step = size / numCores;
        int startIdx, endIdx;
        List<Future<BestMatchingData>> results = new ArrayList<>();

        for (int i = 0; i < numCores; i++) {
            startIdx = i * step;
            if (i == numCores - 1)
                endIdx = dictionary.size();
            else
                endIdx = (i + 1) * step;
            //创建任务
            BestMatchingBasicTask task = new BestMatchingBasicTask(startIdx, endIdx, dictionary, word);
            Future<BestMatchingData> future = executor.submit(task);//执行器提交任务并获取Future接口返回结果
            results.add(future);
        }
        executor.shutdown();

        List<String> words = new ArrayList<String>();
        int minDistance = Integer.MAX_VALUE;
        for (Future<BestMatchingData> future : results) {
            BestMatchingData data = future.get();
            if (data.getDistance() < minDistance) {
                words.clear();
                minDistance = data.getDistance();
                words.addAll(data.getWords());
            } else if (data.getDistance() == minDistance)
                words.addAll(data.getWords());
        }
        BestMatchingData result = new BestMatchingData();
        result.setDistance(minDistance);
        result.setWords(words);
        return result;
    }
}

3. BestMatchingConcurrentMain class

Implement the executable main class main and print the result.

import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;

public class BestMatchingConcurrentMain {
    public static void main(String[] args) {
        try {
            Date startTime,endTime;
            List<String> dictionary= WordsLoader.load("data/UK Advanced Cryptics Dictionary.txt");
            System.out.println("Dictionary Size:"+dictionary.size());

            String word="stitter";//待匹配单词
            if (args.length==1)
                word=args[0];

            startTime=new Date();
            BestMatchingData result=BestMatchingBasicConcurrentCalculation.getBestMatchingWords(word,dictionary);
            endTime=new Date();
            List<String> results=result.getWords();
            System.out.println("Word: "+word);
            System.out.println("Minimum distance: "+result.getDistance());
            System.out.println("耗时:"+(endTime.getTime()-startTime.getTime())+" ms");
            System.out.println("List of best matching words: "+results.size());
            results.forEach(System.out::println);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Six, verification results

1. Serial version results

Test the string stitter, look up the dictionary and get the best match with 9 words:

In order to avoid contingency, a number of test runs were performed, and the serial method took about 240ms .

sitting
skitter
slitter
spitter
Asked
stint
supports
stutter
titter

2. Concurrent version results

The concurrent version has tested and run the results several times, and it takes about 120ms , which is equivalent to half of the serial time, and the performance is improved.

Take the best word matching algorithm as an example, learn the Callable interface and Future interface of Java concurrent programming, understand the internal operating mechanism more deeply and concretely, and at the same time better understand the knowledge through this practical application, dynamic programming, and the shortest edit distance algorithm.

If you think it’s good, welcome to "one-click, three-link", like, bookmark, follow, comment directly if you have any questions, and exchange and learn!


My CSDN blog: https://blog.csdn.net/Charzous/article/details/113338669

Guess you like

Origin blog.csdn.net/Charzous/article/details/113338669