Exception 和 RuntimeException 区别
- 在定义方法时不需要声明会抛出runtime exception; 在调用这个方法时不需要捕获这个runtime exception; runtime exception是从java.lang.RuntimeException或java.lang.Error类衍生出来的。 例如:nullpointexception,IndexOutOfBoundsException就属于runtime exception。
- 定义方法时必须声明所有可能会抛出的exception; 在调用这个方法时,必须使用try语句进行检查或者在方法上加上throws Exception(捕获指的是catch这个语句块,你可以catch之后就地处理,或者用throw关键字继续抛出,或者你不加catch也行,只要你加上throws Exception就会自动抛出,不过我们一般习惯加上catch,在catch中执行日志记录等操作);exception是从java.lang.Exception类衍生出来的。例如:IOException,SQLException就属于Exception。
- 如果自定义异常类完成全局异常功能,一般选择继承runtime exception,在会报异常的地方抛出,然后由异常捕捉器捕捉处理:
/* 检验密码是否正确 */
if (!sysUser.getPassword().equals(Md5Util.createMd5(loginReqVO.getPassword()))) {
throw new BusinessException(ExceptionInfoEnum.ERROR_PASSWORD);
}
/* 全局异常处理器 */
@ExceptionHandler(value = BusinessException.class)
public AjaxResult businessException(BusinessException e) {
log.error(LogConstant.BUSINESSEXCEPTION_PREFIX, e);
return AjaxResult.builder().withSimpleInject(e).build();
}
- throws用于方法上,throw用于方法内。
- 当抛出Exception时必须在方法上throws,抛出RuntimeException时不需要。
- 在使用框架开发时,如果是异常处理器范围外的异常(指不在controller、service、dao直接能调用范围内的程序出现的异常),自己throw后try catch处理就好了。
如果main方法抛出异常,谁来处理?
如下图所示:
实际上是 Thread 默认的异常处理器为我们提供了这个功能,但是是由 JVM 去调用这个功能
Thread.UncaughtExceptionHandler:
/**
* 当线程由于未捕获异常而终止时Java虚拟机将使用getUncaughtExceptionHandler
* 查询线程的UncaughtExceptionHandler并将调用处理程序的uncaughtException方法,
* 将线程和异常作为参数传递。如果未明确设置线程的UncaughtExceptionHandler,
* 则其ThreadGroup对象将作为其UncaughtExceptionHandler。如果ThreadGroup对象
* 对处理异常没有特殊要求,它可以将调用转发给getDefaultUncaughtExceptionHandler
* 默认未捕获的异常处理程序。
*/
@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
ThreadGroup是默认的UncaughtExceptionHandler,如果我们没有自定义UncaughtExceptionHandler那么就将使用ThreadGroup,也可以自定义:
异常对程序造成的影响
总结:
- try中有异常的地方后面的代码都不会执行;catch中将异常继续throw,则catch块后的代码都不会执行
- 如果异常交给JVM处理,异常出现的地方后的代码将不会执行,并且程序直接结束
- 你可以理解try catch只是起到记录日志的作用,我不管加不加try catch,异常都会沿着调用栈往上抛,区别你可以选择在catch中记录完日志后继续throw或者直接停住
线程池的异常处理机制
我们知道,在JAVA中,异常在不同的线程之间是不能共享的,比如,你在父线程中try catch一段代码,这段代码中new了一个子线程,子线程中抛出一个异常,那么其实在父线程中是感知不到这个异常的出现的:
上图中的try catch相当于不存在,主线程和子线程各执行各的,互不相干,
接下来我们看看在线程池中式如何处理线程异常的
一提到线程池,我们第一时间想到的就是juc包下的ThreadPoolExecutor:
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), threadFactory);
ThreadPoolExecutor为我们提供了两种执行自定义任务的方法:
threadPoolExecutor.submit(task);
threadPoolExecutor.execute(task);
submit只能使用try catch在获取子线程异常的时候会造成父线程阻塞:
public class FutureDemo1 {
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), threadFactory);
FutureRunnable runnable = new FutureRunnable();
Future<?> submit = threadPoolExecutor.submit(runnable);
try {
submit.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println("子线程的异常被父线程catch了");
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println("程序结束");
while (true) {
if (Thread.currentThread().isInterrupted()) {
threadPoolExecutor.shutdown();
break;
}
}
}
}
class FutureRunnable implements Runnable {
@Override
public void run() {
try {
System.out.println("程序正在运行");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
throw new RuntimeException("报错了!");
}
}
Future是Java 5添加的类,用来描述一个异步计算的结果,但是获取一个结果时方法较少,要么通过轮询isDone,确认完成后,调用get()获取值,要么调用get()设置一个超时时间。但是这个get()方法会阻塞住调用线程,这种阻塞的方式显然和我们的异步编程的初衷相违背。
为了解决这个问题,JDK吸收了guava的设计思想,加入了Future的诸多扩展功能形成了CompletableFuture:
public class FutureDemo2 {
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), threadFactory);
FutureRunnable2 runnable2 = new FutureRunnable2();
CompletableFuture.runAsync(runnable2, threadPoolExecutor)
.whenComplete((aVoid, throwable) -> System.out.println("程序A结束"))
.exceptionally(throwable -> {
System.out.println("子线程的异常被父线程catch了");
runnable2.flg = false;
return null;
});
System.out.println("我在A执行后马上执行,不会因为子线程而被阻塞");
while (true) {
if (!runnable2.flg) {
System.out.println("程序结束");
threadPoolExecutor.shutdown();
break;
}
}
}
}
class FutureRunnable2 implements Runnable {
volatile boolean flg = true;
@Override
public void run() {
try {
System.out.println("程序A正在运行");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
throw new RuntimeException("A报错了!");
}
}
execute可以结合setUncaughtExceptionHandler来使用达到异步效果:
public class FutureDemo3 {
public static void main(String[] args) {
FutureRunnable3 runnable3 = new FutureRunnable3();
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d")
.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("子线程的异常被自定义UncaughtExceptionHandler处理了");
runnable3.flg = false;
}
})
.build();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), threadFactory);
threadPoolExecutor.execute(runnable3);
while (true) {
if (!runnable3.flg) {
System.out.println("程序结束");
threadPoolExecutor.shutdown();
break;
}
}
}
}
class FutureRunnable3 implements Runnable {
volatile boolean flg = true;
@Override
public void run() {
try {
System.out.println("程序正在运行");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
throw new RuntimeException("报错了!");
}
}
这里通过ThreadFactory来设置UncaughtExceptionHandler,在 threadPoolExecutor.execute(runnable3)后不用等待子线程执行结果,继续往下执行