看源码好处多多,尤其是优秀的框架源码,受益无穷。
日常CRUD的工作实在无聊,把CRUD的代码写一万遍也没法提高个人的层级。
业务的疯狂增长确实能增长个人的技能,因为需要不断思考优化方案,不断去升级(硬件、软件和个人能力)。
但是要想写出好的代码,好的框架还是需要多借鉴别人的优秀代码。
笔者个人的感受就是:明明看了很多遍的设计模式,但是平时写代码还都是一个方法搞定所有,无法用到实战中。所以还是要多看看优秀的源码,这样不自觉中就会向这些代码习惯靠拢,也会不自觉的使用设计模式。
ZookeeperThread引发的思考:
今天在看到Zookeeper源码的核心类SendThread和EventThread的时候,对他们的公有父类ZookeeperThread不太理解,源码如下:
/**
* This is the main class for catching all the uncaught exceptions thrown by the
* threads.
*/
public class ZooKeeperThread extends Thread {
private static final Logger LOG = LoggerFactory
.getLogger(ZooKeeperThread.class);
private UncaughtExceptionHandler uncaughtExceptionalHandler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
handleException(t.getName(), e);
}
};
public ZooKeeperThread(Runnable thread, String threadName) {
super(thread, threadName);
setUncaughtExceptionHandler(uncaughtExceptionalHandler);
}
public ZooKeeperThread(String threadName) {
super(threadName);
setUncaughtExceptionHandler(uncaughtExceptionalHandler);
}
protected void handleException(String thName, Throwable e) {
LOG.warn("Exception occurred from thread {}", thName, e);
}
}
代码看着很简单,从类注释上看出来,这是一个可以捕获 该线程抛出的所有未捕获的异常 的类。
平时笔者从来没写过这种代码,也没有对Thread做过什么特殊处理,所以引发了笔者的好奇。
UncaughtExceptionHandler到底有什么特殊能力呢?
UncaughtExceptionHandler的作用:
当线程由于未捕获异常即将中止时,JVM将使用thread.getuncaughtexceptionhandler()查询线程的uncaughtException处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。如果一个线程没有显式地设置它的UncaughtExceptionHandler,那么它的ThreadGroup对象就充当它的UncaughtExceptionHandler。如果ThreadGroup对象没有处理异常的特殊要求,它可以将调用转发给默认的未捕获异常处理程序。
总体来说:当线程由于一个未捕获的异常突然中止时调用的处理程序的接口。
从这大段文字的描述,我们还是无法直接感受到这个接口的作用,下面来先看个示例
1)常规捕获异常方式
public class NoCaughtThread {
public static void main(String[] args) {
try {
Thread thread = new Thread(new Task());
thread.start();
} catch (Exception e) {
System.out.println("==Exception: " + e.getMessage());
}
}
}
class Task implements Runnable {
@Override
public void run() {
System.out.println(3 / 2);
System.out.println(3 / 0);
System.out.println(3 / 1);
}
}
// res
1
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at com.example.demo.Task.run(NoCaughtThread.java:26)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
从结果值我们来分析下,只输出了一个1,因为到输出第二句3/0的时候报错了,所以抛出了异常ArithmeticException,然后exit出了程序。
我们发现一个问题就是main线程中的catch并没有生效。
总结:因为在多线程环境下,线程抛出的异常是无法用try...catch捕获的。
这样可能会导致一些问题:由于这些未捕获的异常导致的线程中止,可能会使我们在线程中处理的系统资源回收、关闭连接这些操作无法处理。
2)使用UncaughtExceptionHandler处理
我们来将上面的方法改造下
public class WatchCaughtThread {
public static void main(String[] args) {
Thread thread = new Thread(new Task());
thread.setUncaughtExceptionHandler(new LocalUncaughtExceptionHandler());
thread.start();
}
}
// 自定义handler
class LocalUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("==Exception: "+e.getMessage());
// do sth... close connection/release resource and so on.
}
}
// res
1
==Exception: / by zero
通过上述结果可以看到,该未知的异常被成功捕获到,我们可以在uncaughtException()方法中做一些释放资源、连接的操作,避免由于线程的突然中止导致资源无法释放。
3)使用线程池处理线程的UncaughtExceptionHandler做法
我们经常使用jdk线程池来处理线程,那么在线程池中我们该如何自定义UncaughtExceptionHandler异常的处理呢,直接看示例如下:
public class ExecuteCaught {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ThreadPoolTask());
exec.shutdown();
}
}
class ThreadPoolTask implements Runnable {
@Override
public void run() {
// 需要在这里定义UncaughtExceptionHandler
Thread.currentThread().setUncaughtExceptionHandler(new LocalUncaughtExceptionHandler());
System.out.println(3 / 2);
System.out.println(3 / 0);
System.out.println(3 / 1);
}
}
总结:我们对线程池的使用时需要特别注意下,如果要使UncaughtExceptionHandler定义生效,需要在run()方法内部定义。
回顾ZookeeperThread:
我们再来看下ZookeeperThread如何自定义UncaughtExceptionHandler:
// 来自ZookeeperThread.java
private UncaughtExceptionHandler uncaughtExceptionalHandler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
handleException(t.getName(), e);
}
};
// 对于这些未知异常,主要是将这些异常明细信息统一打印到Log中,避免异常的缺漏。
protected void handleException(String thName, Throwable e) {
LOG.warn("Exception occurred from thread {}", thName, e);
}
// 使用UncaughtExceptionHandler方式
public ZooKeeperThread(Runnable thread, String threadName) {
super(thread, threadName);
// 直接在构造方法中添加UncaughtExceptionHandler
setUncaughtExceptionHandler(uncaughtExceptionalHandler);
}
总结:通过对ZookeeperThread的学习,我们看到其对未知异常采用的方式是统一捕获,打印明细到Log中,这样就避免了异常缺漏打印。
总结:
我们在日常的工作中,可以考虑使用这种方式,定义一个基础Thread,统一捕获所有异常,做好处理,极力避免未知异常对程序的影响。
最怕的就是线程异常退出了,但是我们什么日志都抓不到。
参考:
https://blog.csdn.net/weixin_33698823/article/details/90330574