Executor interface in java.util.concurrent package lets you submit Runnable tasks. Executor then handles the execution of the task. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc.

Contract of Executor interface has a single method: execute(Runnable command)

This implementation uses another Executor to run the tasks. It maintains a queue for managing tasks. It guarantees that there is only one task running at a time.

public class SerialExecutor implements Executor {

    private Executor executor;
    private Queue<Runnable> taskQueue = new LinkedList<>();
    private Runnable active = null;

    public static void main(String[] args) {
        Executor executor = new SerialExecutor(Executors.newFixedThreadPool(5));
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("Task completed!");
            }
        });
    }

    public SerialExecutor(Executor executor) {
        this.executor = executor;
    }

    @Override
    public void execute(Runnable command) {
        taskQueue.offer(new Runnable() {
            @Override
            public void run() {
                try {
                    command.run();
                } finally {
                    scheduleNext();
                }
            }
        });

        if (active == null) {
            scheduleNext();
        }
    }

    private void scheduleNext() {
        if ((active = taskQueue.poll()) != null) {
            executor.execute(active);
        }
    }
}