0

I want to create two threads in my application that'll run two methods. I'm using the builder design pattern where inside the build method I have something like this, request is the Object that is passed:

Rules rule;
        Request build() {
            Request request = new Request(this);
        //I want one threat to call this method
        Boolean isExceeding = this.rule.volumeExceeding(request);

        //Another thread to call this method
        Boolean isRepeating = this.rule.volumeRepeating(request);
        
        //Some sort of timer that will wait until both values are received,
        //If one value takes too long to be received kill the thread and continue with
        //whatever value was received.

        ..Logic based on 2 booleans..
        return request;
    }
        
    

Here's how this class looks like:

public class Rules {

    public Boolean volumeExceeding(Request request) {

        ...some...logic...
        return true/false;
    }


    public Boolean volumeRepeating(Request request) {
       
        ...some...logic...
        return true/false;
    }
}

I have commented in the code what I'd like to happen. Basically, I'd like to create two threads that'll run their respective method. It'll wait until both are finished, however, if one takes too long (example: more than 10ms) then return the value that was completed. How do I create this? I'm trying to understand the multithreading tutorials, but the examples are so generic that it's hard to take what they did and apply it to something more complicated.

user2896120
  • 3,180
  • 4
  • 40
  • 100
  • And the bools have default values, right? – akuzminykh Jul 20 '20 at 05:34
  • @akuzminykh What do you mean by default values? Whatever is processed in those respective methods will return either true/false. For example, volumeRepeating could return true, while volumeExceeding could return false. – user2896120 Jul 20 '20 at 05:35
  • I mean: If you say that one thread can be canceled if it takes too long, the corresponding bool has to have a default value, no? You're using both: `..Logic based on 2 booleans..` – akuzminykh Jul 20 '20 at 05:37
  • @akuzminykh ahh I gotcha. Yes, the default value would be false if it takes too long – user2896120 Jul 20 '20 at 05:38
  • Okay, and now when you say: *"[...] however, if one takes too long (example: more than 10ms) then return the value that was completed."* – You're talking about the "outer" method returning that is waiting for the results; not the thread's method, right? – akuzminykh Jul 20 '20 at 05:43
  • @akuzminykh for example, t1 will execute the volumeExceeding method and t2 will execute volumeRepeating. Say t2 completed and t1 took too long. Then t2 will be whatever value it processed and t1 will be the default false. If both didnt return in time, then both would be false – user2896120 Jul 20 '20 at 05:47

2 Answers2

2

One way to do that is to use CompletableFutures:

import java.util.concurrent.CompletableFuture;

class Main {
    
    private static final long timeout = 1_000; // 1 second

    
    static Boolean volumeExceeding(Object request) {
        System.out.println(Thread.currentThread().getName());
        final long startpoint = System.currentTimeMillis();
        
        // do stuff with request but we do dummy stuff
        for (int i = 0; i < 1_000_000; i++) {
            if (System.currentTimeMillis() - startpoint > timeout) {
                return false;
            }
            Math.log(Math.sqrt(i));
        }
        return true;
    }
    
    
    static Boolean volumeRepeating(Object request) {
        System.out.println(Thread.currentThread().getName());
        final long startpoint = System.currentTimeMillis();
        
        // do stuff with request but we do dummy stuff
        for (int i = 0; i < 1_000_000_000; i++) {
            if (System.currentTimeMillis() - startpoint > timeout) {
                return false;
            }
            Math.log(Math.sqrt(i));
        }
        return true;
    }
    

    public static void main(String[] args) {
        final Object request = new Object();
        
        CompletableFuture<Boolean> isExceedingFuture = CompletableFuture.supplyAsync(
                () -> Main.volumeExceeding(request));
        CompletableFuture<Boolean> isRepeatingFuture = CompletableFuture.supplyAsync(
                () -> Main.volumeRepeating(request));
        
        Boolean isExceeding = isExceedingFuture.join();
        Boolean isRepeating = isRepeatingFuture.join();
        
        System.out.println(isExceeding);
        System.out.println(isRepeating);
    }
}

Notice that one task takes significantly longer than the other.

What's happening? You supply those tasks to the common pool by using CompletableFuture for execution. Both tasks are executed by two different threads. What you've asked for is that a task is stopped when it takes too long. Therefore you can simply remember the time when a task has started and periodically check it against a timeout. Important: Do this check when the task would return while leaving the data in a consistent state. Also note that you can place multiple checks of course.

Here's a nice guide about CompletableFuture: Guide To CompletableFuture

akuzminykh
  • 4,522
  • 4
  • 15
  • 36
  • You can also call: Boolean isExceeding = isExceedingFuture.get(10, TimeUnit.SECONDS); This aborts after specified amount of time. Make sure to catch TimeOutException and assign default value then. – Lecagy Jul 20 '20 at 06:21
  • @Lecagy That's true but I personally like my way better. Using the `get` with timeout approach won't stop the underlying task. Therefore you'd be wasting CPU time. – akuzminykh Jul 20 '20 at 06:27
  • Thank you for your answer, what is the difference between .join() and .get()? – user2896120 Jul 20 '20 at 06:48
  • @user2896120 The difference is about the exceptions thrown. Check out [this](https://stackoverflow.com/a/47463228/12323248) answer that explains it. – akuzminykh Jul 20 '20 at 06:58
  • I think I understand your code, but I just want to make sure. Two threads run, each will execute their respective methods. Is there a block that happens with .join() until the result comes back and we are controlling that with time? If it weren't for the time constraint, it would block until the result comes back then the two print statements would print. Is this correct? – user2896120 Jul 20 '20 at 07:03
  • @user2896120 Yes: `join()` blocks until the result is done. The result-calculating method terminates itself by returning `false`, which is the the default result, when it takes too long. So `join()` returns either the default value or the normally calculated value. If you are new to multithreading in Java, have a look on these guides: [Guide to java.util.concurrent.Future](https://www.baeldung.com/java-future), [A Guide to the Java ExecutorService](https://www.baeldung.com/java-executor-service-tutorial) and [Guide To CompletableFuture](https://www.baeldung.com/java-completablefuture). – akuzminykh Jul 20 '20 at 07:17
0

If I understand your question correctly, then you should do this with a ticketing system (also known as provider-consumer pattern or producer-consumer pattern), so your threads are reused (which is a significant performance boost, if those operations are time critical).

The general idea should be:

application initialization

  • Initialize 2 or more "consumer" threads, which can work tickets (also called jobs).

runtime

  • Feed the consumer threads tickets (or jobs) that will be waited on for (about) as long as you like. However depending on the JVM, the waiting period will most likely not be exactly n milliseconds, as most often schedulers are more 'lax' in regards to waiting periods for timeouts. e.g. Thread.sleep() will almost always be off by a bunch of milliseconds (always late, never early - to my knowledge).
  • If the thread does not return after a given waiting period, then that result must be neglected (according to your logic), and the ticket (and thus the thread) must be informed to abort that ticket. It is important that you not interrupt the thread, since that can lead to exceptions, or prevent locks from being unlocked.

Remember, that halting or stopping threads from the outside is almost always problematic with locks, so I would suggest, your jobs visit a possible exit point periodically, so if you stop caring about a result, they can be safely terminated.

TreffnonX
  • 2,924
  • 15
  • 23
  • Thank you for your answer, is @akuzminykh answer what you are talking about? – user2896120 Jul 20 '20 at 06:48
  • Essentially yes, for most cases that will work. My suggestion involves a lot of own code, while that other idea is probably more elegant and requires less code. I am not 100% sure how the threads are treated with CompletableFutures, and whether they are reused, but I think in comparison to the wait clause, that is very little extra runtime, so it probably won't matter. – TreffnonX Jul 20 '20 at 07:58