1

I want to make a test program with CompletableFuture. I have a class with 2 functions:

public class FutureTextData {
    private ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
    private  CompletableFuture<Void> futureForText;

    public void getCharInText(String text){
        futureForText = CompletableFuture.runAsync(() -> {
            for (int i = 0; i < text.length()-3; i++) {
                map.compute(text.substring(i+1),(key,value) -> value+=1);
                map.compute(text.substring(i+2),(key,value) -> value+=1);
                map.compute(text.substring(i+3),(key,value) -> value+=1);
            }
            for(Map.Entry<String ,Integer> entry:map.entrySet()){
                if(entry.getKey().length()==3)
                    System.out.println(entry.getKey());

            }

        });
    }

    public void recordCharInText(String outPutFile){
        /*try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            File file = new File(outPutFile);

            try(BufferedWriter bf = new BufferedWriter(new FileWriter(file))){
                for(Map.Entry<String ,Integer> entry:map.entrySet()){
                    bf.write(entry.getKey() +"<----->" + entry.getValue());

                }

            }catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

In getCharInText(), I want to count the number of certain substrings in the text, and in recordCharInText() I want to record the current state of the Map.

And when I run the program:

FutureTextData futureTextData = new FutureTextData();
futureTextData.getCharInText(result);
futureTextData.recordCharInText("outFile.txt");

Then everything just completes without errors and everything. i.e. map is not written to the file, and getCharInText() is not even executed.

Can you tell me what the error is?

greybeard
  • 2,249
  • 8
  • 30
  • 66
Alpharius
  • 489
  • 5
  • 12
  • The second future may run before the first one is finished. – dan1st Dec 11 '21 at 10:40
  • @dan1st if i just run ```futureTextData.getCharInText(result);``` the program just ends – Alpharius Dec 11 '21 at 10:47
  • 1
    Does this answer your question? [CompletableFuture is not getting executed. If I use the ExecutorService pool its work as expected but not with the default forkJoin common pool](https://stackoverflow.com/questions/51879659/completablefuture-is-not-getting-executed-if-i-use-the-executorservice-pool-its) – Didier L Dec 13 '21 at 21:27

2 Answers2

1

The ForkJoinPool CommonPool which runs your tasks will shut down when the program exits:

    /**
     * Returns the common pool instance. This pool is statically
     * constructed; its run state is unaffected by attempts to {@link
     * #shutdown} or {@link #shutdownNow}. However this pool and any
     * ongoing processing are automatically terminated upon program
     * {@link System#exit}.  Any program that relies on asynchronous
     * task processing to complete before program termination should
     * invoke {@code commonPool().}{@link #awaitQuiescence awaitQuiescence},
     * before exit.
     *
     * @return the common pool instance
     * @since 1.8
     */
    public static ForkJoinPool commonPool() {
        // assert common != null : "static init error";
        return common;
    }

If I modify your code to add awaitQuiescence and log the execution of the tasks I get:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;

public class FutureTextData {
    private ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
    public  CompletableFuture<Void> futureForText;

    public void getCharInText(String text){
        futureForText = CompletableFuture.runAsync(() -> {
            System.out.println("Running first task");
            map.put("foo", 1);
        });
    }

    public void recordCharInText(String outPutFile){
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("Running second task");

            File file = new File(outPutFile);

            try(BufferedWriter bf = new BufferedWriter(new FileWriter(file))){
                for(Map.Entry<String ,Integer> entry:map.entrySet()){
                    bf.write(entry.getKey() +"<----->" + entry.getValue());

                }

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTextData futureTextData = new FutureTextData();
        futureTextData.getCharInText("xyz");
        futureTextData.recordCharInText("outFile.txt");
        ForkJoinPool.commonPool().awaitQuiescence(1000, TimeUnit.MILLISECONDS);
    }
}

which produces:

Running first task
Running second task

Process finished with exit code 0
tgdavies
  • 10,307
  • 4
  • 35
  • 40
1

You intend to compute the map and write the result to a file:

FutureTextData futureTextData = new FutureTextData();
futureTextData.getCharInText(result); // populate map
futureTextData.recordCharInText("outFile.txt"); // write values to a file

  • The current code executes the two operations (compute map and write to file) on two different threads concurrently. But, you want to write the map to file only when the CF in getCharInText has completed.
  • The CFs, by default, use ForkJoinPool CommonPool, which terminates when your program exits. The main thread doesn't wait for the threads in getCharInText and recordCharInText to complete. It calls the methods and returns immediately.

Here's how the code should look like:

public class FutureTextData {

    public CompletableFuture<Map<String, Integer>> getCharInText(String text) {
        return CompletableFuture.supplyAsync(() -> {
            var map = new HashMap<String, Integer>();
            
            for (int i = 0; i < text.length() - 3; i++) {
                // For this case, javadoc suggests to use merge instead of compute
                
               /* map.compute(text.substring(i+1),(key,value) -> value+=1);
                map.compute(text.substring(i+2),(key,value) -> value+=1);
                map.compute(text.substring(i+3),(key,value) -> value+=1);*/
                
                map.merge(text.substring(i + 1), 1, Integer::sum);
                map.merge(text.substring(i + 2), 1, Integer::sum);
                map.merge(text.substring(i + 3), 1, Integer::sum);
            }

            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getKey().length() == 3)
                    System.out.println(entry.getKey());
            }

            return map;
        });
    }

    public void recordCharInText(String outPutFile, Map<String, Integer> map) {
       // write to file logic
    }

    public static void main(String[] args) throws InterruptedException {
        FutureTextData futureTextData = new FutureTextData();
        futureTextData.getCharInText("soeerrt text")
                       // supply the map to next execution step
                      .thenAccept(map -> futureTextData.recordCharInText("outFile.txt", map));

        // wait for the tasks to finish
        ForkJoinPool.commonPool().awaitTermination(5, TimeUnit.SECONDS);
    }

Note:

  • I moved map inside getCharInText. Always try to avoid mutation inside lambdas.
  • The lambdas for the CFs can be a method. Lambdas should be at most 2 lines
adarsh
  • 1,393
  • 1
  • 8
  • 16