Understanding Callable vs Runnable: When Futures Block and How to Avoid It

This article compares Java's Callable and Runnable interfaces, demonstrates how Future.get() blocks, shows alternative submit methods, and provides complete code examples illustrating return values, task completion checks, and practical usage of ThreadPoolExecutor.

BiCaiJia Technology Team
BiCaiJia Technology Team
BiCaiJia Technology Team
Understanding Callable vs Runnable: When Futures Block and How to Avoid It

Java provides two primary interfaces for defining tasks that can be executed by a thread pool: Callable and Runnable . The main advantage of Callable is that it can return a result through a Future, whereas Runnable cannot.

Callable Example and Blocking Behavior

public class MyCallable implements Callable<String> {
    private int age;
    public MyCallable(int age) {
        this.age = age;
    }
    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(8);
        return "返回值 年龄是:" + age;
    }
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable(22);
        int corePoolSize = 2;
        int maximumPoolSize = 3;
        int keepAliveTime = 5;
        TimeUnit unit = TimeUnit.SECONDS;
        LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        Future<String> future = threadPoolExecutor.submit(myCallable);
        try {
            System.out.println(System.currentTimeMillis());
            String string = future.get(); // blocks until callable finishes
            System.out.println(string);
            System.out.println(System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The printed timestamps show that future.get() blocks for about eight seconds, confirming its blocking nature.

Submitting a Runnable and Checking Completion

public class Run {
    public static void main(String[] args) {
        try {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("打印的信息");
                }
            };
            ExecutorService executorService = Executors.newCachedThreadPool();
            Future<?> future = executorService.submit(runnable);
            System.out.println(future.get() + " " + future.isDone());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The output is:

打印的信息
null true

Here future.get() returns null because the runnable has no result, and future.isDone() shows the task completed without blocking.

Using submit(Runnable, T result) to Obtain a Custom Result

public class User {
    private String username;
    private String password;
    // getters and setters omitted
}

public class MyRunnable implements Runnable {
    private User user;
    public MyRunnable(User user) {
        this.user = user;
    }
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        user.setUsername("admin");
        user.setPassword("123456");
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            User user = new User();
            MyRunnable myRunnable = new MyRunnable(user);
            int corePoolSize = 10;
            int maximumPoolSize = 10;
            int keepAliveTime = 10;
            TimeUnit unit = TimeUnit.SECONDS;
            LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
            ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
            Future<User> future = executor.submit(myRunnable, user);
            System.out.println(System.currentTimeMillis());
            System.out.println(user.getUsername() + "-" + user.getPassword()); // prints null-null before task finishes
            user = future.get(); // blocks until runnable finishes
            System.out.println(user.getUsername() + "-" + user.getPassword()); // prints admin-123456
            System.out.println(System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The console output demonstrates that before the task completes the user fields are null, and after future.get() they contain the values set inside the runnable, confirming that submit(Runnable, result) can return a custom object.

Overall, Callable is suitable when a task needs to produce a result, but its Future.get() call blocks until completion. Runnable can be submitted with or without a result, and methods like isDone() allow non‑blocking status checks.

CallableRunnableThreadPoolExecutorFuture
BiCaiJia Technology Team
Written by

BiCaiJia Technology Team

BiCaiJia Technology Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.