最近很多小伙伴都在问在Python的调用者线程中捕获线程的异常和在python的调用者线程中捕获线程的异常这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展Java多线程:捕获线程异
最近很多小伙伴都在问在Python的调用者线程中捕获线程的异常和在python的调用者线程中捕获线程的异常这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展Java 多线程:捕获线程异常、Java中捕获线程异常的方式有哪些、java主线程捕获子线程中的异常、Java多线程:捕获线程异常等相关知识,下面开始了哦!
本文目录一览:- 在Python的调用者线程中捕获线程的异常(在python的调用者线程中捕获线程的异常)
- Java 多线程:捕获线程异常
- Java中捕获线程异常的方式有哪些
- java主线程捕获子线程中的异常
- Java多线程:捕获线程异常
在Python的调用者线程中捕获线程的异常(在python的调用者线程中捕获线程的异常)
一般而言,我对Python和多线程编程非常陌生。基本上,我有一个脚本会将文件复制到另一个位置。我希望将其放置在另一个线程中,以便可以输出....
以指示脚本仍在运行。
我遇到的问题是,如果无法复制文件,它将引发异常。如果在主线程中运行,这没关系;但是,使用以下代码不起作用:
try: threadClass = TheThread(param1, param2, etc.) threadClass.start() ##### **Exception takes place here**except: print "Caught an exception"
在线程类本身中,我尝试重新抛出异常,但是它不起作用。我已经看到这里的人问类似的问题,但是他们似乎都在做比我想做的事情更具体的事情(而且我不太了解所提供的解决方案)。我见过有人提到的用法sys.exc_info()
,但是我不知道在哪里或如何使用它。
非常感谢所有帮助!
编辑:线程类的代码如下:
class TheThread(threading.Thread): def __init__(self, sourceFolder, destFolder): threading.Thread.__init__(self) self.sourceFolder = sourceFolder self.destFolder = destFolder def run(self): try: shul.copytree(self.sourceFolder, self.destFolder) except: raise
答案1
小编典典问题是thread_obj.start()
立即返回。你产生的子线程在其自己的上下文中使用自己的堆栈执行。在那里发生的任何异常都在子线程的上下文中,并且在其自己的堆栈中。我现在想到的一种将此信息传达给父线程的方法是使用某种消息传递,因此你可能会对此进行研究。
尝试以下尺寸:
import sysimport threadingimport Queueclass ExcThread(threading.Thread): def __init__(self, bucket): threading.Thread.__init__(self) self.bucket = bucket def run(self): try: raise Exception(''An error occured here.'') except Exception: self.bucket.put(sys.exc_info())def main(): bucket = Queue.Queue() thread_obj = ExcThread(bucket) thread_obj.start() while True: try: exc = bucket.get(block=False) except Queue.Empty: pass else: exc_type, exc_obj, exc_trace = exc # deal with the exception print exc_type, exc_obj print exc_trace thread_obj.join(0.1) if thread_obj.isAlive(): continue else: breakif __name__ == ''__main__'': main()
Java 多线程:捕获线程异常
你处理过多线程中的异常吗?如何捕获多线程中发生的异常?捕获子线程的异常与捕获当前线程的异常一样简单吗?
除了 try catch。Java 中还可以通过异常处理器 UncaughtExceptionHandler 来处理那些未捕获的异常。
# 在当前线程捕获当前线程发生的异常:
/**
* @author futao
* @date 2020/6/17
*/
@Slf4j
public class ExceptionInCurThread {
public static void main(String[] args) {
try {
throw new RuntimeException("在主线程抛出异常,在主线程捕获");
} catch (RuntimeException e) {
log.error("捕获到异常", e);
}
}
}
- 结果:
image.png
- 结论:在当前线程通过 try catch 可以捕获当前线程抛出的异常。
# 可以在当前通过 try catch 的方式捕获其他线程抛出的异常吗?''
/**
* @author 喜欢天文的pony站长
* Created on 2020/6/16.
*/
public class ExceptionInChildThread implements Runnable {
@Override
public void run() {
throw new RuntimeException("子线程发生了异常...");
}
/**
* 模拟子线程发生异常
*
* @throws InterruptedException
*/
private static void exceptionThread() throws InterruptedException {
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
}
/**
* 在主线程尝试通过try catch捕获异常
*/
private static void catchInMain() {
try {
exceptionThread();
} catch (Exception e) {
//无法捕获发生在其他线程中的异常
log.error("捕获到了异常?", e);
}
}
public static void main(String[] args) throws InterruptedException {
ExceptionInChildThread.catchInMain();
}
}
- (错误的) 预期:
- 在运行第一个线程的时候发生了异常,被 catch 捕获,打印捕获到了异常?和异常堆栈且后面的线程将不会运行。
- 实际运行结果:
- 并不符合预期。
- 没有被 try catch 捕获。
- 后续的线程没有因为第一个线程发生异常而跳过。
image.png
- 结论:
- 无法在一个线程中通过 try catch 捕获另外一个线程的异常。
# 解决方案
- 在每个线程内部 run () 方法内通过 try catch 捕获当前线程发生的异常。
- 缺点:每个线程都需要编写重复的 try catch 代码
- 使用线程异常处理器 UncaughtExceptionHandler
- 给所有线程设置统一的异常处理器
- 给每个线程设置特定的异常处理器
- 给线程组设置异常处理器
- 给线程池设置异常处理器
- 因为线程池也是通过 new Thread () 的方式创建的线程,所以思想与上面两种方法一致。
- 注意:execute () 与 submit () 方式对 异常处理的不同。
# 在线程内部 run () 通过 try catch 捕获异常
/**
* @author 喜欢天文的pony站长
* Created on 2020/6/16.
*/
@Slf4j
public class ExceptionInChildThread implements Runnable {
@Override
public void run() {
try {
//do something else...
throw new RuntimeException("子线程发生了异常...");
} catch (Exception e) {
log.error("在线程内部捕获异常", e);
}
}
/**
* 模拟子线程发生异常
*
* @throws InterruptedException
*/
private static void exceptionThread() throws InterruptedException {
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
}
/**
* 在主线程尝试通过try catch捕获异常
*/
private static void catchInMain() {
try {
exceptionThread();
} catch (Exception e) {
//无法捕获发生在其他线程中的异常
log.error("捕获到了异常?", e);
}
}
public static void main(String[] args) throws InterruptedException {
ExceptionInChildThread.catchInMain();
}
}
- 结果:
- 成功在子线程内部 run () 方法捕获到了异常
image.png
# 使用线程异常处理器 UncaughtExceptionHandler
当一个线程由于未捕获异常而退出时,JVM 会把这个事件报告给应用程序提供的 UncaughtExceptionHandler 异常处理器
- 自定义线程异常处理器
/**
* 自定义线程未捕获异常处理器
*
* @author futao
* @date 2020/6/17
*/
public class CustomThreadUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomThreadUncaughtExceptionHandler.class);
@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("捕获到线程发生的异常,线程信息:[{}]", JSON.toJSONString(t), e);
}
}
- 使用:
1. 全局:
- Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
- 通过调用 Thread 的静态方法 setDefaultUncaughtExceptionHandler (),设置 Thread 的静态属性 defaultUncaughtExceptionHandler. 为我们自定义的异常处理器。
- 源码:
- 测试:
/**
* @author 喜欢天文的pony站长
* Created on 2020/6/16.
*/
@Slf4j
public class ExceptionInChildThread implements Runnable {
@Override
public void run() {
throw new RuntimeException("子线程发生了异常...");
}
/**
* 模拟子线程发生异常
*
* @throws InterruptedException
*/
private static void exceptionThread() throws InterruptedException {
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
}
public static void main(String[] args) throws InterruptedException {
//设置全局的线程异常处理器
Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
exceptionThread();
}
}
- 结果:成功捕获
image.png
2. 为指定线程设置特定的异常处理器
- 细心的同学已经发现了,在上面 Thread 类的截图中,还有一个实例属性 private volatile UncaughtExceptionHandler uncaughtExceptionHandler;。通过给这个属性赋值,可以实现为每个线程对象设置不同的异常处理器。
- 测试使用
/**
* @author 喜欢天文的pony站长
* Created on 2020/6/16.
*/
@Slf4j
public class ExceptionInChildThread implements Runnable {
@Override
public void run() {
throw new RuntimeException("子线程发生了异常...");
}
/**
* 模拟子线程发生异常
*
* @throws InterruptedException
*/
private static void exceptionThread() throws InterruptedException {
Thread thread1 = new Thread(new ExceptionInChildThread());
//为指定线程设置特定的异常处理器
thread1.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
thread1.start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
new Thread(new ExceptionInChildThread()).start();
TimeUnit.MILLISECONDS.sleep(200L);
}
public static void main(String[] args) throws InterruptedException {
exceptionThread();
}
}
- 结果:成功捕获线程 1 的异常信息
3. 线程组
/**
* @author futao
* @date 2020/6/20
*/
@Slf4j
public class ExceptionInThreadGroup implements Runnable {
@Override
public void run() {
throw new RuntimeException("线程任务发生了异常");
}
public static void main(String[] args) throws InterruptedException {
ThreadGroup threadGroup = new ThreadGroup("只知道抛出异常的线程组...") {
@Override
public void uncaughtException(Thread t, Throwable e) {
super.uncaughtException(t, e);
log.error("线程组内捕获到线程[{},{}]异常", t.getId(), t.getName(), e);
}
};
ExceptionInThreadGroup exceptionInThreadGroup = new ExceptionInThreadGroup();
new Thread(threadGroup, exceptionInThreadGroup, "线程1").start();
TimeUnit.MILLISECONDS.sleep(300L);
//优先获取绑定在thread对象上的异常处理器
Thread thread = new Thread(threadGroup, exceptionInThreadGroup, "线程2");
thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
thread.start();
TimeUnit.MILLISECONDS.sleep(300L);
new Thread(threadGroup, exceptionInThreadGroup, "线程3").start();
}
}
- 结果:
4. 线程池
/**
* @author futao
* @date 2020/6/17
*/
public class CatchThreadPoolException {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
4,
1L,
TimeUnit.MINUTES,
new LinkedBlockingDeque<>(1024),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设置线程异常处理器
thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
return thread;
}
}
);
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
throw new RuntimeException("execute()发生异常");
}
}
);
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
throw new RuntimeException("submit.run()发生异常");
}
});
threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
throw new RuntimeException("submit.call()发生异常");
}
});
threadPoolExecutor.shutdown();
}
}
- 结果:并不符合预期,预期应该捕获三个异常
- 只捕获到了通过 execute () 提交的任务的异常
- 没有捕获到通过 submit () 提交的任务的异常
- 通过 afterExecute () 捕获 submit () 任务的异常
- 通过 submit () 方法的源码可以发现,submit () 是将 runnable () 封装成了 RunnableFuture<Void>,并最终调用 execute (ftask); 执行。
/**
* @author futao
* @date 2020/6/17
*/
@Slf4j
public class CatchThreadPoolException {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
4,
1L,
TimeUnit.MINUTES,
new LinkedBlockingDeque<>(1024),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设置线程异常处理器
thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
return thread;
}
}
) {
/**
* 捕获{@code FutureTask<?>}抛出的异常
*
* @param r
* @param t
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (r instanceof FutureTask<?>) {
try {
//get()的时候会将异常内的异常抛出
((FutureTask<?>) r).get();
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
log.error("捕获到线程的异常返回值", e);
}
}
//Throwable t永远为null,拿不到异常信息
//log.error("afterExecute中捕获到异常,", t);
}
};
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
throw new RuntimeException("execute()发生异常");
}
}
);
TimeUnit.MILLISECONDS.sleep(200L);
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
throw new RuntimeException("submit.run()发生异常");
}
});
TimeUnit.MILLISECONDS.sleep(200L);
threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
throw new RuntimeException("submit.call()发生异常");
}
}).get(); //get()的时候会将异常抛出
threadPoolExecutor.shutdown();
}
}
Java中捕获线程异常的方式有哪些
这篇文章主要介绍“Java中捕获线程异常的方式有哪些”,在日常操作中,相信很多人在Java中捕获线程异常的方式有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中捕获线程异常的方式有哪些”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
首先,我们要知道,在Java中,线程中的异常是不能抛出到调用该线程的外部方法中捕获的。
为什么不能抛出到外部线程捕获?
因为线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。
基于这样的设计理念,在Java中,线程方法的异常都应该在线程代码边界之内(run方法内)进行try catch并处理掉。
换句话说,我们不能捕获从线程中逃逸的异常。
怎么进行的限制?
通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束。
如果在线程中抛出了线程会怎么样?
线程会立即终结。
现在我们可以怎样捕获线程中的异常?
Java中在处理异常的时候,通常的做法是使用try-catch-finally来包含代码块,但是Java自身还有一种方式可以处理——使用UncaughtExceptionHandler。
它能检测出某个线程由于未捕获的异常而终结的情况。
当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器(这是Thread类中的接口):
//Thread类中的接口 public interface UncaughtExceptionHanlder { void uncaughtException(Thread t, Throwable e); }
JDK5之后允许我们在每一个Thread对象上添加一个异常处理器UncaughtExceptionHandler 。
Thread.UncaughtExceptionHandler.uncaughtException()方法会在线程因未捕获的异常而面临死亡时被调用。
首先要先定义一个异常捕获器:
public class MyUnchecckedExceptionhandler implements UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("捕获异常处理方法:" + e); } }
方法1. 创建线程时设置异常处理Handler
Thread t = new Thread(new ExceptionThread()); t.setUncaughtExceptionHandler(new MyUnchecckedExceptionhandler()); t.start();
方法2. 使用Executors创建线程时,还可以在ThreadFactory中设置
ExecutorService exec = Executors.newCachedThreadPool(new ThreadFactory(){ @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setUncaughtExceptionHandler(new MyUnchecckedExceptionhandler()); return thread; } }); exec.execute(new ExceptionThread());
不过,上面的结果能证明:通过execute方式提交的任务,能将它抛出的异常交给异常处理器。
如果改成submit方式提交任务,则异常不能被异常处理器捕获,这是为什么呢?
查看源码后可以发现,如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。
所以,通过submit提交到线程池的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分,因此不会交由异常处理器来处理。
java.util.concurrent.FutureTask 源码
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING)//如果任务没有结束,则等待结束 s = awaitDone(false, 0L); return report(s);//如果执行结束,则报告执行结果 } @SuppressWarnings("unchecked") private V report(int s) throws ExecutionException { Object x = outcome; if (s == norMAL)//如果执行正常,则返回结果 return (V)x; if (s >= CANCELLED)//如果任务被取消,调用get则报CancellationException throw new CancellationException(); throw new ExecutionException((Throwable)x);//执行异常,则抛出ExecutionException }
方法3. 使用线程组ThreadGroup
//1.创建线程组 ThreadGroup threadGroup = // 这是匿名类写法 new ThreadGroup("group") { // 继承ThreadGroup并重新定义以下方法 // 在线程成员抛出unchecked exception 会执行此方法 @Override public void uncaughtException(Thread t, Throwable e) { //4.处理捕获的线程异常 } }; //2.创建Thread Thread thread = new Thread(threadGroup, new Runnable() { @Override public void run() { System.out.println(1 / 0); } }, "my_thread"); //3.启动线程 thread.start();
方法4. 默认的线程异常捕获器
如果我们只需要一个线程异常处理器处理线程的异常,那么我们可以设置一个默认的线程异常处理器,当线程出现异常时,
如果我们没有指定线程的异常处理器,而且线程组也没有设置,那么就会使用默认的线程异常处理器
// 设置默认的线程异常捕获处理器 Thread.setDefaultUncaughtExceptionHandler(new MyUnchecckedExceptionhandler());
上面说的4种方法都是基于线程异常处理器实现的,接下来将的几种方法则不需要依赖异常处理器。
方法5. 使用FetureTask来捕获异常
//1.创建FeatureTask FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { return 1/0; } }); //2.创建Thread Thread thread = new Thread(futureTask); //3.启动线程 thread.start(); try { Integer result = futureTask.get(); } catch (InterruptedException e) { e.printstacktrace(); } catch (ExecutionException e) { //4.处理捕获的线程异常 }
方法6.利用线程池提交线程时返回的Feature引用
//1.创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); //2.创建Callable,有返回值的,你也可以创建一个线程实现Callable接口。 // 如果你不需要返回值,这里也可以创建一个Thread即可,在第3步时submit这个thread。 Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { return 1/0; } }; //3.提交待执行的线程 Future<Integer> future = executorService.submit(callable); try { Integer result = future.get(); } catch (InterruptedException e) { e.printstacktrace(); } catch (ExecutionException e) { //4.处理捕获的线程异常 }
实现原理可以看一下方法2的说明。
方法6本质上和方法5一样是基于FutureTask实现的。
方法7.重写ThreadPoolExecutor的afterExecute方法
//1.创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()) { @Override protected void afterExecute(Runnable r, Throwable t) { if (r instanceof Thread) { if (t != null) { //处理捕获的异常 } } else if (r instanceof FutureTask) { FutureTask futureTask = (FutureTask) r; try { futureTask.get(); } catch (InterruptedException e) { e.printstacktrace(); } catch (ExecutionException e) { //处理捕获的异常 } } } }; Thread t1 = new Thread(() -> { int c = 1 / 0; }); threadPoolExecutor.execute(t1); Callable<Integer> callable = () -> 2 / 0; threadPoolExecutor.submit(callable);
到此,关于“Java中捕获线程异常的方式有哪些”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注小编网站,小编会继续努力为大家带来更多实用的文章!
java主线程捕获子线程中的异常
<divid="content_views"> <p>本文主要参考:《think in java》</p><p>好,下面上货。</p><p></p><div><span>正常情况下,如果不做特殊的处理,在主线程中是不能够捕获到子线程中的异常的。</span></div><div><span>例如下面的情况。</span></div><pre onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div><span>package</span> com.xueyou.demo.theadexceptiondemo;</div></div></li><li><div><divdata-line-number="2"></div></div><div><div> </div></div></li><li><div><divdata-line-number="3"></div></div><div><div><span>public</span> <span><span>class</span> <span>ThreadExceptionRunner</span> <span>implements</span> <span>Runnable</span></span>{</div></div></li><li><div><divdata-line-number="4"></div></div><div><div> <span>@Override</span></div></div></li><li><div><divdata-line-number="5"></div></div><div><div> <span><span>public</span> <span>void</span> <span>run</span><span>()</span> </span>{</div></div></li><li><div><divdata-line-number="6"></div></div><div><div> <span>throw</span> <span>new</span> RuntimeException(<span>"error !!!!"</span>);</div></div></li><li><div><divdata-line-number="7"></div></div><div><div> }</div></div></li><li><div><divdata-line-number="8"></div></div><div><div>}</div></div></li></ol></code><divdata-title="复制"></div></pre><div><span>使用线程执行上面的任务</span></div><pre onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div><span>package</span> com.xueyou.demo.theadexceptiondemo;</div></div></li><li><div><divdata-line-number="2"></div></div><div><div> </div></div></li><li><div><divdata-line-number="3"></div></div><div><div><span>import</span> com.sun.glass.ui.TouchInputSupport;</div></div></li><li><div><divdata-line-number="4"></div></div><div><div> </div></div></li><li><div><divdata-line-number="5"></div></div><div><div><span>import</span> java.util.concurrent.ExecutorService;</div></div></li><li><div><divdata-line-number="6"></div></div><div><div><span>import</span> java.util.concurrent.Executors;</div></div></li><li><div><divdata-line-number="7"></div></div><div><div><span>import</span> java.util.concurrent.ThreadFactory;</div></div></li><li><div><divdata-line-number="8"></div></div><div><div> </div></div></li><li><div><divdata-line-number="9"></div></div><div><div><span>public</span> <span><span>class</span> <span>ThreadExceptionDemo</span> </span>{</div></div></li><li><div><divdata-line-number="10"></div></div><div><div> <span><span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(String[] args)</span> </span>{</div></div></li><li><div><divdata-line-number="11"></div></div><div><div> <span>try</span> {</div></div></li><li><div><divdata-line-number="12"></div></div><div><div> Thread thread = <span>new</span> Thread(<span>new</span> ThreadExceptionRunner());</div></div></li><li><div><divdata-line-number="13"></div></div><div><div> thread.start();</div></div></li><li><div><divdata-line-number="14"></div></div><div><div> } <span>catch</span> (Exception e) {</div></div></li><li><div><divdata-line-number="15"></div></div><div><div> System.out.println(<span>"========"</span>);</div></div></li><li><div><divdata-line-number="16"></div></div><div><div> e.printStackTrace();</div></div></li><li><div><divdata-line-number="17"></div></div><div><div> } <span>finally</span> {</div></div></li><li><div><divdata-line-number="18"></div></div><div><div> }</div></div></li><li><div><divdata-line-number="19"></div></div><div><div> System.out.println(<span>123</span>);</div></div></li><li><div><divdata-line-number="20"></div></div><div><div> }</div></div></li><li><div><divdata-line-number="21"></div></div><div><div>}</div></div></li></ol></code><divdata-title="复制"></div></pre><div><span>执行结果如下:</span></div><p><img src="https://img-blog.csdn.net/20180625224448285?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dpbGQ0NmNhdA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt=""><br></p><p></p><div><span>如果想要在主线程中捕获子线程的异常,我们需要使用ExecutorService,同时做一些修改。</span></div><div><span>如下:</span></div><br><pre onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div><span>package</span> com.xueyou.demo.theadexceptiondemo;</div></div></li><li><div><divdata-line-number="2"></div></div><div><div> </div></div></li><li><div><divdata-line-number="3"></div></div><div><div><span>import</span> com.sun.glass.ui.TouchInputSupport;</div></div></li><li><div><divdata-line-number="4"></div></div><div><div> </div></div></li><li><div><divdata-line-number="5"></div></div><div><div><span>import</span> java.util.concurrent.ExecutorService;</div></div></li><li><div><divdata-line-number="6"></div></div><div><div><span>import</span> java.util.concurrent.Executors;</div></div></li><li><div><divdata-line-number="7"></div></div><div><div><span>import</span> java.util.concurrent.ThreadFactory;</div></div></li><li><div><divdata-line-number="8"></div></div><div><div> </div></div></li><li><div><divdata-line-number="9"></div></div><div><div><span>public</span> <span><span>class</span> <span>ThreadExceptionDemo</span> </span>{</div></div></li><li><div><divdata-line-number="10"></div></div><div><div> <span><span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(String[] args)</span> </span>{</div></div></li><li><div><divdata-line-number="11"></div></div><div><div> <span>try</span> {</div></div></li><li><div><divdata-line-number="12"></div></div><div><div> Thread thread = <span>new</span> Thread(<span>new</span> ThreadExceptionRunner());</div></div></li><li><div><divdata-line-number="13"></div></div><div><div> thread.start();</div></div></li><li><div><divdata-line-number="14"></div></div><div><div> } <span>catch</span> (Exception e) {</div></div></li><li><div><divdata-line-number="15"></div></div><div><div> System.out.println(<span>"========"</span>);</div></div></li><li><div><divdata-line-number="16"></div></div><div><div> e.printStackTrace();</div></div></li><li><div><divdata-line-number="17"></div></div><div><div> } <span>finally</span> {</div></div></li><li><div><divdata-line-number="18"></div></div><div><div> }</div></div></li><li><div><divdata-line-number="19"></div></div><div><div> System.out.println(<span>123</span>);</div></div></li><li><div><divdata-line-number="20"></div></div><div><div> ExecutorService exec = Executors.newCachedThreadPool(<span>new</span> HandleThreadFactory());</div></div></li><li><div><divdata-line-number="21"></div></div><div><div> exec.execute(<span>new</span> ThreadExceptionRunner());</div></div></li><li><div><divdata-line-number="22"></div></div><div><div> exec.shutdown();</div></div></li><li><div><divdata-line-number="23"></div></div><div><div> }</div></div></li><li><div><divdata-line-number="24"></div></div><div><div>}</div></div></li><li><div><divdata-line-number="25"></div></div><div><div> </div></div></li><li><div><divdata-line-number="26"></div></div><div><div><span><span>class</span> <span>MyUncaughtExceptionHandle</span> <span>implements</span> <span>Thread</span>.<span>UncaughtExceptionHandler</span> </span>{</div></div></li><li><div><divdata-line-number="27"></div></div><div><div> <span>@Override</span></div></div></li><li><div><divdata-line-number="28"></div></div><div><div> <span><span>public</span> <span>void</span> <span>uncaughtException</span><span>(Thread t, Throwable e)</span> </span>{</div></div></li><li><div><divdata-line-number="29"></div></div><div><div> System.out.println(<span>"caught "</span> + e);</div></div></li><li><div><divdata-line-number="30"></div></div><div><div> }</div></div></li><li><div><divdata-line-number="31"></div></div><div><div>}</div></div></li><li><div><divdata-line-number="32"></div></div><div><div> </div></div></li><li><div><divdata-line-number="33"></div></div><div><div><span><span>class</span> <span>HandleThreadFactory</span> <span>implements</span> <span>ThreadFactory</span> </span>{</div></div></li><li><div><divdata-line-number="34"></div></div><div><div> <span>@Override</span></div></div></li><li><div><divdata-line-number="35"></div></div><div><div> <span><span>public</span> Thread <span>newThread</span><span>(Runnable r)</span> </span>{</div></div></li><li><div><divdata-line-number="36"></div></div><div><div> System.out.println(<span>"create thread t"</span>);</div></div></li><li><div><divdata-line-number="37"></div></div><div><div> Thread t = <span>new</span> Thread(r);</div></div></li><li><div><divdata-line-number="38"></div></div><div><div> System.out.println(<span>"set uncaughtException for t"</span>);</div></div></li><li><div><divdata-line-number="39"></div></div><div><div> t.setUncaughtExceptionHandler(<span>new</span> MyUncaughtExceptionHandle());</div></div></li><li><div><divdata-line-number="40"></div></div><div><div> <span>return</span> t;</div></div></li><li><div><divdata-line-number="41"></div></div><div><div> }</div></div></li><li><div><divdata-line-number="42"></div></div><div><div> </div></div></li><li><div><divdata-line-number="43"></div></div><div><div>}</div></div></li></ol></code><divdata-title="复制"></div></pre><div><span>这样就能够捕获到异常了,运行结果如下:</span></div><p><br></p><p><img src="https://img-blog.csdn.net/20180625224611971?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dpbGQ0NmNhdA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt=""><br></p><p></p><div><span>上面的方式是设置每一个线程执行时候的异常处理。如果每一个线程的异常处理相同,我们可以用如下的方式进行处理,使用Thread的静态方法。</span></div><div><span>Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandle());</span></div><p></p><div><span>整体代码如下:</span></div><br><pre onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div>package com.xueyou.demo.theadexceptiondemo;</div></div></li><li><div><divdata-line-number="2"></div></div><div><div> </div></div></li><li><div><divdata-line-number="3"></div></div><div><div><span>import</span> com.sun.glass.ui.TouchInputSupport;</div></div></li><li><div><divdata-line-number="4"></div></div><div><div> </div></div></li><li><div><divdata-line-number="5"></div></div><div><div><span>import</span> java.util.concurrent.ExecutorService;</div></div></li><li><div><divdata-line-number="6"></div></div><div><div><span>import</span> java.util.concurrent.Executors;</div></div></li><li><div><divdata-line-number="7"></div></div><div><div><span>import</span> java.util.concurrent.ThreadFactory;</div></div></li><li><div><divdata-line-number="8"></div></div><div><div> </div></div></li><li><div><divdata-line-number="9"></div></div><div><div>/**</div></div></li><li><div><divdata-line-number="10"></div></div><div><div> * Created by wuxueyou on <span>2018</span>/<span>6</span>/<span>24.</span></div></div></li><li><div><divdata-line-number="11"></div></div><div><div> */</div></div></li><li><div><divdata-line-number="12"></div></div><div><div>public <span><span><span>class</span> <span>ThreadExceptionDemo</span> {</span></span></div></div></li><li><div><divdata-line-number="13"></div></div><div><div><span> <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(String[] args)</span> {</span></div></div></li><li><div><divdata-line-number="14"></div></div><div><div><span> <span>try</span> {</span></div></div></li><li><div><divdata-line-number="15"></div></div><div><div><span> <span>Thread</span> <span>thread</span> = <span>new</span> <span>Thread</span><span>(new ThreadExceptionRunner<span>()</span>)</span>;</span></div></div></li><li><div><divdata-line-number="16"></div></div><div><div><span> <span>thread</span>.<span>start</span><span>()</span>;</span></div></div></li><li><div><divdata-line-number="17"></div></div><div><div><span> } <span>catch</span> <span>(Exception e)</span> {</span></div></div></li><li><div><divdata-line-number="18"></div></div><div><div><span> <span>System</span>.<span>out</span>.<span>println</span><span>(<span>"========"</span>)</span>;</span></div></div></li><li><div><divdata-line-number="19"></div></div><div><div><span> <span>e</span>.<span>printStackTrace</span><span>()</span>;</span></div></div></li><li><div><divdata-line-number="20"></div></div><div><div><span> } <span>finally</span> {</span></div></div></li><li><div><divdata-line-number="21"></div></div><div><div><span> }</span></div></div></li><li><div><divdata-line-number="22"></div></div><div><div><span> <span>System</span>.<span>out</span>.<span>println</span><span>(<span>123</span>)</span>;</span></div></div></li><li><div><divdata-line-number="23"></div></div><div><div><span> <span>Thread</span>.<span>setDefaultUncaughtExceptionHandler</span><span>(new MyUncaughtExceptionHandle<span>()</span>)</span>;</span></div></div></li><li><div><divdata-line-number="24"></div></div><div><div><span>// <span>ExecutorService</span> <span>exec</span> = <span>Executors</span>.<span>newCachedThreadPool</span><span>(new HandleThreadFactory<span>()</span>)</span>;</span></div></div></li><li><div><divdata-line-number="25"></div></div><div><div><span> <span>ExecutorService</span> <span>exec</span> = <span>Executors</span>.<span>newCachedThreadPool</span><span>()</span>;</span></div></div></li><li><div><divdata-line-number="26"></div></div><div><div><span> <span>exec</span>.<span>execute</span><span>(new ThreadExceptionRunner<span>()</span>)</span>;</span></div></div></li><li><div><divdata-line-number="27"></div></div><div><div><span> <span>exec</span>.<span>shutdown</span><span>()</span>;</span></div></div></li><li><div><divdata-line-number="28"></div></div><div><div><span></span></div></div></li><li><div><divdata-line-number="29"></div></div><div><div><span> }</span></div></div></li><li><div><divdata-line-number="30"></div></div><div><div><span>}</span></div></div></li><li><div><divdata-line-number="31"></div></div><div><div><span></span></div></div></li></ol></code><divdata-title="复制"></div></pre><div><span>运行结果:</span></div><img src="https://img-blog.csdn.net/2018062522474239?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dpbGQ0NmNhdA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt=""><br> </div>
Java多线程:捕获线程异常
你处理过多线程中的异常吗?如何捕获多线程中发生的异常?捕获子线程的异常与捕获当前线程的异常一样简单吗?
除了try catch。Java中还可以通过异常处理器UncaughtExceptionHandler来处理那些未捕获的异常。
# 在当前线程捕获当前线程发生的异常:
/** * @author futao * @date 2020/6/17 */@Slf4jpublic class ExceptionInCurThread { public static void main(String[] args) { try { throw new RuntimeException("在主线程抛出异常,在主线程捕获"); } catch (RuntimeException e) { log.error("捕获到异常", e); } } }
-
结果:
image.png
-
结论:在当前线程通过try catch可以捕获当前线程抛出的异常。
# 可以在当前通过try catch的方式捕获其他线程抛出的异常吗?''
/** * @author 喜欢天文的pony站长 * Created on 2020/6/16. */public class ExceptionInChildThread implements Runnable { @Override public void run() { throw new RuntimeException("子线程发生了异常..."); } /** * 模拟子线程发生异常 * * @throws InterruptedException */ private static void exceptionThread() throws InterruptedException { new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); } /** * 在主线程尝试通过try catch捕获异常 */ private static void catchInMain() { try { exceptionThread(); } catch (Exception e) { //无法捕获发生在其他线程中的异常 log.error("捕获到了异常?", e); } } public static void main(String[] args) throws InterruptedException { ExceptionInChildThread.catchInMain(); } }
-
(错误的)预期:
-
在运行第一个线程的时候发生了异常,被catch捕获,打印捕获到了异常?和异常堆栈且后面的线程将不会运行。
-
-
实际运行结果:
-
并不符合预期。
-
没有被try catch捕获。
-
后续的线程没有因为第一个线程发生异常而跳过。
-
image.png
-
结论:
-
无法在一个线程中通过try catch捕获另外一个线程的异常。
-
# 解决方案
-
在每个线程内部run()方法内通过try catch捕获当前线程发生的异常。
-
缺点:每个线程都需要编写重复的try catch 代码
-
使用线程异常处理器UncaughtExceptionHandler
-
给所有线程设置统一的异常处理器
-
给每个线程设置特定的异常处理器
-
给线程组设置异常处理器
-
给线程池设置异常处理器
-
因为线程池也是通过new Thread()的方式创建的线程,所以思想与上面两种方法一致。
-
注意:execute()与submit()方式对 异常处理的不同。
# 在线程内部run()通过try catch捕获异常
/** * @author 喜欢天文的pony站长 * Created on 2020/6/16. */@Slf4jpublic class ExceptionInChildThread implements Runnable { @Override public void run() { try { //do something else... throw new RuntimeException("子线程发生了异常..."); } catch (Exception e) { log.error("在线程内部捕获异常", e); } } /** * 模拟子线程发生异常 * * @throws InterruptedException */ private static void exceptionThread() throws InterruptedException { new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); } /** * 在主线程尝试通过try catch捕获异常 */ private static void catchInMain() { try { exceptionThread(); } catch (Exception e) { //无法捕获发生在其他线程中的异常 log.error("捕获到了异常?", e); } } public static void main(String[] args) throws InterruptedException { ExceptionInChildThread.catchInMain(); } }
-
结果:
-
成功在子线程内部run()方法捕获到了异常
-
image.png
# 使用线程异常处理器UncaughtExceptionHandler
当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器
-
自定义线程异常处理器
/** * 自定义线程未捕获异常处理器 * * @author futao * @date 2020/6/17 */public class CustomThreadUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(CustomThreadUncaughtExceptionHandler.class); @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("捕获到线程发生的异常,线程信息:[{}]", JSON.toJSONString(t), e); } }
-
使用:
1. 全局:
-
Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
-
通过调用Thread的静态方法setDefaultUncaughtExceptionHandler(),设置Thread的静态属性defaultUncaughtExceptionHandler.为我们自定义的异常处理器。
-
源码:
-
-
测试:
/** * @author 喜欢天文的pony站长 * Created on 2020/6/16. */@Slf4jpublic class ExceptionInChildThread implements Runnable { @Override public void run() { throw new RuntimeException("子线程发生了异常..."); } /** * 模拟子线程发生异常 * * @throws InterruptedException */ private static void exceptionThread() throws InterruptedException { new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); } public static void main(String[] args) throws InterruptedException { //设置全局的线程异常处理器 Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler()); exceptionThread(); } }
-
结果: 成功捕获
image.png
2. 为指定线程设置特定的异常处理器
-
细心的同学已经发现了,在上面Thread类的截图中,还有一个实例属性private volatile UncaughtExceptionHandler uncaughtExceptionHandler;。通过给这个属性赋值,可以实现为每个线程对象设置不同的异常处理器。
-
测试使用
/** * @author 喜欢天文的pony站长 * Created on 2020/6/16. */@Slf4jpublic class ExceptionInChildThread implements Runnable { @Override public void run() { throw new RuntimeException("子线程发生了异常..."); } /** * 模拟子线程发生异常 * * @throws InterruptedException */ private static void exceptionThread() throws InterruptedException { Thread thread1 = new Thread(new ExceptionInChildThread()); //为指定线程设置特定的异常处理器 thread1.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler()); thread1.start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); new Thread(new ExceptionInChildThread()).start(); TimeUnit.MILLISECONDS.sleep(200L); } public static void main(String[] args) throws InterruptedException { exceptionThread(); } }
-
结果: 成功捕获线程1的异常信息
3. 线程组
/** * @author futao * @date 2020/6/20 */@Slf4jpublic class ExceptionInThreadGroup implements Runnable { @Override public void run() { throw new RuntimeException("线程任务发生了异常"); } public static void main(String[] args) throws InterruptedException { ThreadGroup threadGroup = new ThreadGroup("只知道抛出异常的线程组...") { @Override public void uncaughtException(Thread t, Throwable e) { super.uncaughtException(t, e); log.error("线程组内捕获到线程[{},{}]异常", t.getId(), t.getName(), e); } }; ExceptionInThreadGroup exceptionInThreadGroup = new ExceptionInThreadGroup(); new Thread(threadGroup, exceptionInThreadGroup, "线程1").start(); TimeUnit.MILLISECONDS.sleep(300L); //优先获取绑定在thread对象上的异常处理器 Thread thread = new Thread(threadGroup, exceptionInThreadGroup, "线程2"); thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler()); thread.start(); TimeUnit.MILLISECONDS.sleep(300L); new Thread(threadGroup, exceptionInThreadGroup, "线程3").start(); } }
-
结果:
4. 线程池
/** * @author futao * @date 2020/6/17 */public class CatchThreadPoolException { public static void main(String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2, 4, 1L, TimeUnit.MINUTES, new LinkedBlockingDeque<>(1024), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); //设置线程异常处理器 thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler()); return thread; } } ); threadPoolExecutor.execute(new Runnable() { @Override public void run() { throw new RuntimeException("execute()发生异常"); } } ); threadPoolExecutor.submit(new Runnable() { @Override public void run() { throw new RuntimeException("submit.run()发生异常"); } }); threadPoolExecutor.submit(new Callable<String>() { @Override public String call() throws Exception { throw new RuntimeException("submit.call()发生异常"); } }); threadPoolExecutor.shutdown(); } }
-
结果: 并不符合预期,预期应该捕获三个异常
-
只捕获到了通过execute()提交的任务的异常
-
没有捕获到通过submit()提交的任务的异常
-
-
通过afterExecute()捕获submit()任务的异常
-
通过submit()方法的源码可以发现,submit()是将runnable()封装成了RunnableFuture<Void>,并最终调用execute(ftask);执行。
-
/** * @author futao * @date 2020/6/17 */@Slf4jpublic class CatchThreadPoolException { public static void main(String[] args) throws InterruptedException, ExecutionException { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2, 4, 1L, TimeUnit.MINUTES, new LinkedBlockingDeque<>(1024), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); //设置线程异常处理器 thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler()); return thread; } } ) { /** * 捕获{@code FutureTask<?>}抛出的异常 * * @param r * @param t */ @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (r instanceof FutureTask<?>) { try { //get()的时候会将异常内的异常抛出 ((FutureTask<?>) r).get(); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } catch (ExecutionException e) { log.error("捕获到线程的异常返回值", e); } } //Throwable t永远为null,拿不到异常信息 //log.error("afterExecute中捕获到异常,", t); } }; threadPoolExecutor.execute(new Runnable() { @Override public void run() { throw new RuntimeException("execute()发生异常"); } } ); TimeUnit.MILLISECONDS.sleep(200L); threadPoolExecutor.submit(new Runnable() { @Override public void run() { throw new RuntimeException("submit.run()发生异常"); } }); TimeUnit.MILLISECONDS.sleep(200L); threadPoolExecutor.submit(new Callable<String>() { @Override public String call() throws Exception { throw new RuntimeException("submit.call()发生异常"); } }).get(); //get()的时候会将异常抛出 threadPoolExecutor.shutdown(); } }
欢迎在评论区留下你看文章时的思考,及时说出,有助于加深记忆和理解,还能和像你一样也喜欢这个话题的读者相遇~
# 本文源代码
-
https://github.com/FutaoSmile/learn-thread/tree/master/src/main/java/com/futao/learn/threads/捕获线程异常
干货分享
最近将个人学习笔记整理成册,使用PDF分享。djcc 领取!
今天的关于在Python的调用者线程中捕获线程的异常和在python的调用者线程中捕获线程的异常的分享已经结束,谢谢您的关注,如果想了解更多关于Java 多线程:捕获线程异常、Java中捕获线程异常的方式有哪些、java主线程捕获子线程中的异常、Java多线程:捕获线程异常的相关知识,请在本站进行查询。
本文标签: