Search code examples
javamultithreadingwait

Wait a specific amount of time but not disturb any other function of the application


In my game I'm going to implement a function that changes the value of a variable linearly for 5 seconds from 100 to 0. The function has to be launched by the player touching a button.

First, I wanted to do it just by a for loop with TimeUnit.MILLISECONDS.sleep() and the value decrementation inside it:

for (int i = 0; i <= 100; i++) {
    decrementBatteryLevel();
    try {
        TimeUnit.MILLISECONDS.sleep(50);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

But I quickly found out that this will cause my entire game to stop for 5 seconds, which is not what I want. So the first thing I actually tried was creating a thread running the for loop:

private Thread useBattery = new Thread(() -> {
    for (int i = 0; i <= 100; i++) {
        decrementBatteryLevel();
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

And calling it when necessary:

useBattery.start();

This worked, but only for the first launch. The second launch of the useBattery Thread resulted throwing java.lang.IllegalThreadStateException by the game.

I understood that I can't start a thread that is already alive, so I tried doing this:

if (useBattery.isAlive()) {
    useBattery.interrupt();
}
useBattery.start();

But I ended up with java.lang.IllegalThreadStateException being thrown again. /* Is a Thread a single-use product...? */

The final thing I tried and that actually worked was creating a new Thread every time I needed. So here's what I currently have:

new Thread(() -> {
    for (int i = 0; i <= 100; i++) {
        decrementBatteryLevel();
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();

The result of that is satisfying, but I'm afraid that doing so, I quickly fill up RAM with unnecessary, "used up" Threads. Is there any way to do it better?

Thank you.


Solution

  • Use an ExecutorService to launch tasks.

    Executors.newSingleThreadExecutor();
    

    You can then submit your Runnable or Callable to get a hold of a corresponding Future.

    final Future<?> submittedResult = executorService.submit(() -> { /* Your code */ });
    

    When launching a new task instance, simply cancel and submit

    submittedResult.cancel(true);
    executorService.submit(() -> { /* Your code */ });
    

    I think you can expand my answer for your specific usecase.
    Also, see https://techblog.bozho.net/interrupting-executor-tasks/