跳至主要内容

Exception Handle in Executor

Exception Handle in Executor

Today, we start with a question: can following code compiles?

// pre-condition: Thread#sleep throws InterruptedException

ExecutorService service = Executors.newCachedThreadPool();
service.submit(() -> {
    Thread.sleep(1);
    return null;
});      
service.submit(() -> {
    Thread.sleep(1);
});

The short answer is no and the compiler will complain the second invocation of sleep but not the first one:

service.submit(() -> {
    Thread.sleep(1); // <------ here
});

Submit A Task

So what happens here? Let’s see the prototype of submit:

<T> Future<T> submit(Callable<T> task);

Very normal and meet our imagination. But in the second submit, do we really give a Callable? No, we don’t return in the second submission. It’s actually a Runnable.

Future<?> submit(Runnable task);

Comparing the prototype of run and call give us the detailed answer :

public interface Runnable {
    public abstract void run();
}

public interface Callable<V> {
    V call() throws Exception;
}

In the first submission, we give executor a callable, which return a null, so we can choose to not handle the interrupt exception. In the second one, we give executor a runnable, in which we have to handle the exception in run().

service.submit(new Callable<Void>() {
    @Override
    public Void call() throws Exception {
        Thread.sleep(1); // <--- fine, for call throw exception
        return null;
    }
});
service.submit(new Runnable() {
    @Override
    public void run() {
        Thread.sleep(1); // compiler error, unhandled exception
    }
});

We enjoy the convenience of lambda, so we have to bear the misleading semantic it may bring.

Exception Handle in Executor

Through the above question, we get to know that we have to handle exception directly in run(). But through which way we can handle the exception threw in call? By some searching, we can find out the answer:

When we submit a Callable job to executor, it will return a Future to us. When we try to get the result of job by calling Future.get(), it will throw ExecutionException if provided callable threw exception in the past (the exception is stored in the Future). And the detailed exception can be inspected using the Throwable.getCause() method.

Comparison

Now, we get to know there exists two different ways to handle exception when using executor to run our jobs.

  • The first is to handle it right in the run;
  • The second is to handle it when getting the result from Future

As far as I am concerned, using the callable give us more choice to decide when to catch our exception and when to throw it to upper code, which is more flexible and powerful to use. Maybe this is the reason why it is introduced with executor frameworks.

One more thing to notice is using Runnable#run may cause some strange symptom that some task was the disappeared when thread dump. This is because some unchecked exception is thrown and executor just silent swallow it and will make programmer very confused. Even though we can register UncaughtExceptionHandler, we may met the tricky differences between submit and execute, in which UncaughtExceptionHandler will not be called as this SO question described.


The famous saying said: “Throw early, catch late”, but it is not so easy to decide when to catch it, isn’t it?

More

Written with StackEdit.

评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation

Implement isdigit

It is seems very easy to implement c library function isdigit , but for a library code, performance is very important. So we will try to implement it and make it faster. Function So, first we make it right. int isdigit ( char c) { return c >= '0' && c <= '9' ; } Improvements One – Macro When it comes to performance for c code, macro can always be tried. #define isdigit (c) c >= '0' && c <= '9' Two – Table Upper version use two comparison and one logical operation, but we can do better with more space: # define isdigit(c) table[c] This works and faster, but somewhat wasteful. We need only one bit to represent true or false, but we use a int. So what to do? There are many similar functions like isalpha(), isupper ... in c header file, so we can combine them into one int and get result by table[c]&SOME_BIT , which is what source do. Source code of ctype.h : # define _ISbit(bit) (1 << (