1

I wrote an android app for killing background running processes which is performed in a background thread. And I use below class to create my own simple thread framework.

public final class ThreadPool {
    private static final ExecutorService sES = Executors.newCachedThreadPool();

    public static Future<?> runNow(Runnable task) {
        return sES.submit(task);
    }
}

However, a serious problem occured. That is the exception(unchecked exception) would be consumed by Executor framework quitely. So I don't know why the ActivityManager.killBackgroundProcesses() method does not work. After spending 2 or 3 hours and I wrote some log at almost every method invoke point, I found this method requires android.permission.KILL_BACKGROUND_PROCESSES permission, otherwise, it would throw a SecurityException that is an unchecked exception. The key is this exception is consumed by Excecutor framework, so I cannot see any exception information at logcat and the app does not crash at all and runs weird. Of course, I don't know that at first, so I spent a lot of time to find out that reason, mainly depending on two posts: Handling exceptions from Java ExecutorService tasks and Catching thread exceptions from Java ExecutorService

So I changed the my ThreadPool class as:

public final class ThreadPool {
    private static final ExecutorService sES = Executors.newCachedThreadPool();

    /*
     * submit(Runnable) and execute(Runnable) method has 
     * big difference. Especially in Exception handling!!!
     * You have to pay attention.
     */
    public static Future<?> submitNow(Runnable task) {
        return sES.submit(task);
    }

    public static void executeNow(Runnable task) {
        sES.execute(task);
    }
}

But I still have below question:

  1. Why Sun/Oracle decide to consume the exception instead of transfer to users to handle if submit(Runnable command) method is used?
  2. How can I change this behavior to handle unchecked exception according to my own need if I insist using submit() method?

And my doubts are:

  1. If submit(Runnable command) method is used, I know the exception result can be get by Future.get() method. But, if we use Future.get() method to judge if an exception occured, the thread Future object located in would be block. That is not what we expect in most case, I suppose.
  2. I also learned Executor.execute() method handles exception like common Thread.start(). But there is no return value. So the task cannot be shut down at any time. Users have no ability to shut down any running thread by Future.cancel() method when leaving activity.
Community
  • 1
  • 1
peacepassion
  • 1,918
  • 2
  • 15
  • 19

3 Answers3

1
  1. If you call get() on the future, you will get an ExecutionException if the underlying operation (callable) threw an exception. See the docs.

  2. You can't change this behavior (from point 1. )

The reason why this is implemented this way is the following: submit is a non blocking call. The job gets posted in the executor and executed at a later time. Only when the job is executed do you know if it crashed or not, so only when you try to access the result of the job do you get the exception.

morpheus05
  • 4,772
  • 2
  • 32
  • 47
  • Do you have any ideas why SUN/ORACLE handle exception in Executor framework in that way? Does it bring any benefit? I think it is bad if the unchecked exception is consumed quietly. – peacepassion Jul 30 '14 at 00:54
  • Added a simple explanation why this is implemented this way. – morpheus05 Jul 30 '14 at 07:25
  • That make some sense. But the default behavior should at least print exception stack trace to the console. However, it shows nothing when happened. – peacepassion Jul 30 '14 at 23:43
  • Why? The don't handle the exception and a simple print to the console means handle the exception. I mean somehow you always have to access the result of the background operation (in a thread safe manner) so you call get() anyway. – morpheus05 Jul 31 '14 at 08:18
0

Finally, I find a good solution.

We can extend Thread and invoke setUncaughtHandler() in the constructor like below.

public class MyThread1 extends Thread {

public MyThread1(Runnable task) {
    super(task);
    setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("thread throws an uncaught exception at thread id: " + t.getId());
        }
    });
}

}

And then customize a ThreadFactory like following.

    public class MyThreadFactory1 implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            return new MyThread1(r, "Peace");
        }
}

So we can call the factory method in Executors like following.

ExecutorService es = Executors.newSingleThreadExecutor(new MyThreadFactory1());

So we can detect the uncaught exception happened in thread.

peacepassion
  • 1,918
  • 2
  • 15
  • 19
-1

In a threaded environment, unchecked exceptions are known to be notorious and weird behaviors could occur like threads dying, no exception log etc.

One good way is to wrap the runnable object in a thread. Create a thread group and add the thread to the thread group.

final ThreadGroup group = new ThreadGroup("<a name for the thread group>");

public static Future<?> submitNow(Runnable task) {
   //Create a thread wrapping the runnable task and specify the thread group
   Thread t = new Thread(group,task);
   return sES.submit(task);
}

The ThreadGroup class has its uncaughtException(Thread, Throwable) method which is automatically called by the JVM if a thread encountered an exception and is uncaught in your code. See http://developer.android.com/reference/java/lang/ThreadGroup.html

You can also change the behavior by creating your own ThreadGroup object and overriding the uncaughtException method:

public class MyThreadGroup extends ThreadGroup {
   @Override
   public void uncaughtException(Thread t, Throwable e) {
      //do what you need to do to handle the exception
   }
}

Or you can assign an UncaughtExceptionHandler to the current Thread.

public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {

   @Override
   public void uncaughtException(Thread t, Throwable t2) { 
     //Implement
   }
}

Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());

Or set the default exception handler:

Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
arjaynacion
  • 369
  • 1
  • 4
  • 10
  • Apparently, the ThreadPool has been deprecated because of its defect. – peacepassion Jul 30 '14 at 01:20
  • @peacepassion What do you mean by the ThreadPool being deprecated? Please at least give a link to prove your comment about deprecation. And what specific defect are you referring to? Also I did not mention about ThreadPool but I did mention about ThreadGroup. – arjaynacion Jul 30 '14 at 04:08
  • 1. Sorry for the wrong word ThreadPool. It should be ThreadGroup. 2. Here is the first sentence of the web you provide: ThreadGroup is a means of organizing threads into a hierarchical structure. This class is obsolete. See Effective Java Item 73, "Avoid thread groups" for details. Link is: http://developer.android.com/reference/java/lang/ThreadGroup.html – peacepassion Jul 30 '14 at 07:07
  • @peacepassion - It was considered obsolete but not officially deprecated. Forget about ThreadGroup, My last advise still applies From Effective Java 73: Prior to release 1.5, there was one small piece of functionality that was available only with the ThreadGroup API: the ThreadGroup.uncaughtException method was the only way to gain control when a thread threw an uncaught exception. This functionality is useful, for example, to direct stack traces to an application- specific log. As of release 1.5, however, the same functionality is available with Thread’s setUncaughtExceptionHandler method. – arjaynacion Jul 31 '14 at 05:28