java多线程异常丢失

问题出现

前2天在zipkin上面观察链路,发现一旦出现feign的远程调用,就会报SQL异常,用traceId到服务器上面搜索,又找不到错误日志,很是诡异。

然后排查了一下,翻了下代码也没找到什么头绪。最开始怀疑是logback输出日志级别不够,调整成了debug也不行。然后又怀疑是fegin调用的问题,也没什么收获。

后面仔细翻了下代码,发现代码是去记录操作日志,但是里面是用线程池处理的。类似如下

System.out.println("start................");
        ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2);
        threadPoolExecutor.submit(new Callable() {
            /**
             * Computes a result, or throws an exception if unable to do so.
             *
             * @return computed result
             * @throws Exception if unable to compute a result
             */
            @Override
            public Void call() throws Exception {
                // todo: 日志记录
                return null;
            }
        });
        System.out.println("end................");

定位问题

看到这段代码,乍一看没什么问题,但是为什么服务器上面没有错误日志,那很有可能就是丢失了,由于多线程?

验证一把,在call()方法中手动抛出异常,上面代码修改如下进行测试

System.out.println("start................");
        ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2);
        threadPoolExecutor.submit(new Callable() {
            /**
             * Computes a result, or throws an exception if unable to do so.
             *
             * @return computed result
             * @throws Exception if unable to compute a result
             */
            @Override
            public Void call() throws Exception {
                // todo: 日志记录
                throw new RuntimeException("抛出测试异常");
//                return null;
            }
        });
        System.out.println("end................");

写个测试用例跑一下,发现真的没有异常,那就看怎么才能打印出异常呢?再改一下代码

System.out.println("start................");
        ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2);
        threadPoolExecutor.execute(() -> {
           // todo 记录日志
           throw new RuntimeException("抛出测试异常");
        });
        System.out.println("end................");

写个测试用例跑一下,发现异常打印出来了

start................
end................
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: 抛出测试异常
	at com.example.threads.CallableTest.lambda$main$0(CallableTest.java:13)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

那么问题就在于,Callable接口会封装异常,Runnable会直接抛出异常,更重要的是,这个场景没有正确的使用多线程...

任务驱动选择

后来又翻了一下《Java并发编程实战》,有这么一句,那么Callable和Runnable的区别也就出来了

java多线程异常丢失_第1张图片

具体的实现区别在后面吧,从源码分析一下

创建任务

public  Future submit(Callable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
protected  RunnableFuture newTaskFor(Callable callable) {
        return new FutureTask(callable);
    }

submit添加任务会创建一个FutureTask任务

public FutureTask(Callable callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

会把刚才的Callable,赋予callable对象,用在后面执行执行

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable 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);
        }
    }

重点是异常处理那里

java多线程异常丢失_第2张图片

 看下setException方法,里面会把异常放入outcome里面,所以在执行的过程中,调用处是拿不到异常的

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

那么,这个异常调用方,要怎么获取呢?玄妙就在get()里面

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

看下report方法,如果是任务取消了,会抛出CancellationException。

如果是任务执行中有异常,会抛出ExecutionException

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);
    }

你可能感兴趣的:(JAVA,多线程,生产故障,java)