线程池中的线程在执行过程中发生异常的处理措施

线程池中的一个线程异常了会被怎么处理?

  1. 抛异常出来并打印在控制台上(只对了一半,根据提交方式的不同(execute和 submit))

  2. 其他线程任务不受影响

  3. 异常线程会被回收

下面进行验证:

1.抛异常出来并打印在控制台上?

熟悉Executors线程池(本文线程池都是指Executors)都知道 有两种提交线程的方式execute和submit方式,下面将以这两种提交方式来验证。

public class ExecutorsTest {

    public static void main(String[] args) throws Exception {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5
                , 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(4), SmallTool.getCustomThreadFactory()
                , (r, executor) -> SmallTool.printTimeAndThread(" 正在放弃任务:" + r + " , 所属executor : " + executor));
        pool.execute(() -> sayHi("execute"));
        Thread.sleep(1000);
        pool.submit(() -> sayHi("submit"));
    }

    public static void sayHi(String name) {
        String printStr = "thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name;
        System.out.println(printStr);
        throw new RuntimeException(printStr + " error!!!");
    }
}

结果:
thread-name:pool-llc-thread-1,执行方式:execute
Exception in thread "pool-llc-thread-1" java.lang.RuntimeException: thread-name:pool-llc-thread-1,执行方式:execute error!!!
    at com.orion.base.ExecutorsTest.sayHi(ExecutorsTest.java:23)
    at com.orion.base.ExecutorsTest.lambda$main$1(ExecutorsTest.java:15)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
thread-name:pool-llc-thread-3,执行方式:submit

从运行结果可见:

execute执行方式抛出异常显示在控制台了。
submit执行方式并没有显示。

众所周知submit底层其实也是调用的execute,因此它也有异常只是处理方法不一样,它们的区别是:

1、execute没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口
2、submit返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口

submit的话,我们可以在其返回的future中拿到结果。稍微修改下代码,将其异常打印出来即可。

public class ExecutorsTest {

    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5
                , 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(4), SmallTool.getCustomThreadFactory()
                , (r, executor) -> SmallTool.printTimeAndThread(" 正在放弃任务:" + r + " , 所属executor : " + executor));
        pool.execute(() -> sayHi("execute"));
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Future future = pool.submit(() -> sayHi("submit"));
        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            System.out.println("execution Exception : " + e.getMessage());
        }
    }

    public static void sayHi(String name) {
        String printStr = "thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name;
        System.out.println(printStr);
        throw new RuntimeException(printStr + " error!!!");
    }
}

运行结果:
thread-name:pool-llc-thread-1,执行方式:execute
Exception in thread "pool-llc-thread-1" java.lang.RuntimeException: thread-name:pool-llc-thread-1,执行方式:execute error!!!
    at com.orion.base.ExecutorsTest.sayHi(ExecutorsTest.java:33)
    at com.orion.base.ExecutorsTest.lambda$main$1(ExecutorsTest.java:13)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
thread-name:pool-llc-thread-3,执行方式:submit
execution Exception : java.lang.RuntimeException: thread-name:pool-llc-thread-3,执行方式:submit error!!!

造成这种区别,只能去查看源码到底是怎么回事。

execute

根据代码直接点进来,找到在java.util.concurrent.ThreadPoolExecutor#runWorker中抛出了运行异常:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

可以见到,它抛出了异常,最终还是会去到java.lang.ThreadGroup#uncaughtException进行了异常处理:

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

因为没有自定义UncaughtExceptionHandler ,所以使用默认的,

System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");

可见上面打印的方式和这里是一致的。

异常处理有多种方式,详情可见Java线程池「异常处理」正确姿势:有病就得治

submit

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

可见submit底层也是调用execute,但是它会先包装成一个futureTask,它有自己的run方法

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

可以看到,catch那里有个setException,进去看看,debug一下:

    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

get()方法可见会将outcome的包装成一个ExecutionException再扔出来,就验证来上面打印的是execution Exception

2. 其他线程任务不受影响?

public class ExecutorsTest {

    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1
                , 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(16), SmallTool.getCustomThreadFactory());

        for (int i = 0; i < 10; i++) {
            String s = (i == 3) ? "executeException" : "execute";
            pool.execute(() -> sayHi(s));
        }
    }

    public static void sayHi(String name) {
        if ("executeException".equals(name)) {
            throw new RuntimeException(Thread.currentThread().getName() + " -- execute exception");
        } else {
            System.out.println(Thread.currentThread().getName() + " -- execute normal");
        }
    }
}

运行结果:
pool-llc-thread-1 -- execute normal
pool-llc-thread-1 -- execute normal
pool-llc-thread-1 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
pool-llc-thread-2 -- execute normal
Exception in thread "pool-llc-thread-1" java.lang.RuntimeException: pool-llc-thread-1 -- execute exception
    at com.orion.base.ExecutorsTest.sayHi(ExecutorsTest.java:23)
    at com.orion.base.ExecutorsTest.lambda$main$0(ExecutorsTest.java:17)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

由上可见,当有一个线程发生异常时,其他线程是不受影响的。

异常线程会被回收?

但是线程标号已经超过maxPoolSize,默认threadFactory的标号中使用atomicInteger来递增的。为什么会出现该情况,其实总归还是在 ThreadPoolExecutor#runWorker()方法中的processWorkerExit(w, completedAbruptly) 中。

查看代码:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

可见它是先从workers中删除掉,再addWorker,addWorker就会创建线程,线程标号就会增加。

参考: 一个线程池中的线程异常了,那么线程池会怎么处理这个线程?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容