How do I make a single-task FILO background thread?

Solution for How do I make a single-task FILO background thread?
is Given Below:

I have a bunch of threads that spawn somewhat arbitrarily. When they are racing each other, only the one that spawned last is relevant. The other threads can be thrown away or stopped. But I am not sure how to do that, so I have implemented a very basic counter that checks whether the thread is the latest spawned thread.

edit: I would like to be able to kill threads that are taking too long (as they are no longer necessary); probably not from within the threads themselves as they are busy doing something else.

This code works, it seems. But it doesn’t feel robust. Can someone give me a hint toward a proper way to do this?

class Main {
    private static volatile int latestThread = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            spawnThread();
        }
    }

    private static void spawnThread() {
        latestThread++;
        int thisThread = latestThread;
        new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (latestThread == thisThread) {
                // only the latest "active" thread is relevant
                System.out.println("I am the latest thread! " + thisThread);
            }
        }).start();
    }
}

output:

I am the latest thread! 10

code in replit.com

ThreadPoolExecutor is almost what I need, specifically DiscardOldestPolicy. You can set the queue size to 1, so one thread is running and one thread is in the queue, and the oldest in the queue just gets shunted. Clean!

But it finishes two threads (not only the latest), which is not 100% what I was looking for. Although arguably good enough:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class DiscardOldest {

    private static int threadCounter = 1;

    public static void main(String[] args) throws InterruptedException {
        int poolSize = 0;
        int maxPoolSize = 1;
        int queueSize = 1;
        long aliveTime = 1000;
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(queueSize);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, maxPoolSize, aliveTime, TimeUnit.MILLISECONDS, queue, new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 0; i < 4; i++) {
            spawnThread(executor);
        }
    }

    private static void spawnThread(ThreadPoolExecutor executor) {
      final int thisThread = threadCounter++;
      System.out.println(thisThread + " spawning");
        executor.execute(() -> {
            try { 
                Thread.sleep(100);
                System.out.println(thisThread + " finished!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

Ouput:

1 spawning
2 spawning
3 spawning
4 spawning
1 finished!
4 finished!

Rather than relaying on an index, a born time could be set. If there’s a younger thread (was born later) the thread should terminate its execution.

public class Last {
    private static volatile long latestThread = 0L;

    /**
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            spawnThread(System.nanoTime(), i);
        }
    }

    private static void spawnThread(long startTime, int index) {
        new Thread(() -> {
            latestThread = startTime;
            long thisThread = startTime;
            boolean die = false;
            try {
                while (!die) {
                    Thread.sleep(1);
                    if (thisThread < latestThread) {
                        System.out.println(
                                index + ": I am not the latest thread :-(nt" + thisThread + "nt" + latestThread);
                        die = true;
                    } else if (thisThread == latestThread) {
                        System.out.println(
                                index + ": Yes! This is the latest thread!nt" + thisThread + "nt" + latestThread);
                        Thread.sleep(1);
                        System.out.println("Bye!");
                        die = true;
                    }
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

Result:

0: I am not the latest thread :-(
    39667589567880
    39667602317461
2: Yes! This is the latest thread!
    39667602317461
    39667602317461
1: I am not the latest thread :-(
    39667602257160
    39667602317461
Bye!

I did a little research based on comments from everybody (thanks!) and ThreadPoolExecutor is almost what I need, but I want a pool with the total size of 1 (no queue) that kills the active thread once a new thread comes along, which is not allowed in a thread pool and not in line with what a ThreadPool is for. So instead, I came up with a reference to the active thread, and when a new thread comes a long it kills the old one, which seems to do what I want:

import java.util.concurrent.atomic.AtomicInteger;

public class Interrupt {

    private static final AtomicInteger CURRENT_THREAD = new AtomicInteger(0);
    private static Thread activeThread = new Thread(() -> {});

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 4; i++) {
            spawnThread();
            Thread.sleep(3);
        }
    }

    private static void spawnThread() {
        if (activeThread.isAlive()) {
            activeThread.interrupt();
        }
        activeThread = new Thread(() -> {
            int thisThread = CURRENT_THREAD.incrementAndGet();
            System.out.println(thisThread + " working");
            try {
                Thread.sleep(1000);
                System.out.println(thisThread + " finished!");
            } catch (InterruptedException ignored) {}
        });
        activeThread.start();
    }
}

Output:

3 working
2 working
1 working
4 working
4 finished!