Это внутренний класс класса Thread, который является интерфейсом.
связанная информация
Класс реализации:
- ThreadGroup
Обзор
Когда поток завершается из-за неперехваченного исключения, некоторые последующие действия могут выполняться классом реализации этого интерфейса.
Когда поток завершается из-за исключения, JVM запросит объект UncaughtExceptionHandler потока и вызовет метод uncaughtException обработчика, передав объект потока и объект исключения исключения в качестве параметров.Если поток не отображает setUncaughtExceptionHandler, то поток ThreadGroup будет использоваться как его UncaughtExceptionHandler. Если вы используете ThreadGroup по умолчанию, uncaughtException группы ThreadGroup фактически вызывает thread.getDefaultUncaughtExceptionHandler, а последней ролью uncaughtException является ExceptionHandler по умолчанию.
метод
void uncaughtException(Thread t,Throwable e)
Этот метод вызывается JVM, если поток завершается из-за исключений. Любое исключение, созданное в этом методе, игнорируется системой. Этот метод является спасительной соломинкой после того, как подпоток выполнит свою задачу.
Parameters:
t - the thread
e - the exception
пример кода
Сцены
Чтобы продемонстрировать использование этого обработчика, мы можем подумать о сценарии использования этого обработчика.
Например, каждая бизнес-система обычно содержитслужба входа в систему, служба входа в основном является независимой микрослужбой. Как правило, в интерфейсе входа выполняются некоторые вспомогательные операции, такие какжурнал журналподождите, и мы хотим, чтобы этот типВспомогательные операции не будут блокировать нормальный возврат основного интерфейса службы входа в случае возникновения исключения..
Более традиционный подход заключается в использовании промежуточного программного обеспечения сообщений для инкапсуляции пользовательской информации об успешном входе в сообщение и отправки ее в удаленную службу для асинхронной обработки. Но для системы малого и среднего бизнеса, если вы внедрите промежуточное программное обеспечение сообщений, то есть MQ, это принесет много затрат на обслуживание.Широко известны как зенитные орудия для уничтожения комаров.. Существует также более общий способ поместить сообщение журнала об успешном входе в очередь блокировки Java, а затем использовать пул потоков для выделенияограниченные ресурсыМедленно записывайте информацию журнала в базу данных, чтобы запрос на вход возвращался нормально, а журнал обрабатывался асинхронно пулом потоков, чтобы вы не вводили стороннее промежуточное ПО, а стоимость обслуживания не была высокой, вам нужно только хорошо изучить Java.
Хотя этот способ очень удобен, на самом деле есть проблемы:
Исключение записи в базу данных приведет к потере сообщения
Когда выполняется операция вставки в базу данных, может оказаться, что ресурсов пула соединений с базой данных недостаточно, и при достижении тайм-аута соединения будет выдано исключение.В это время поток, выполняющий эту задачу, будет завершен, и это сообщение будет быть потеряны, что приведет к неточным записям журнала вашего входа в систему. Чтобы избежать этой проблемы,Мы можем использовать Thread.UncaughtExceptionHandler, чтобы снова поместить журнал в очередь блокировки после создания исключения..
Если проблема плотных ресурсов пула соединений с базой данных только мгновенная, то это нормально, в противном случае проблемы все равно будут:
Если ресурсы пула соединений с базой данных ограничены в течение длительного времени, хранилище потоков продолжает создавать исключения, помещая задачи обратно в очередь, а нагрузка на интерфейс входа в систему по-прежнему высока, что приводит к тому, что скорость использования очереди не поспевает за очередью. скорость роста, тогда:
Если для буферизации записей журнала используется ограниченная очередь, очередь будет заполнена, и записи журнала нельзя будет поместить в очередь.
Если для буферизации записей журнала используется неограниченная очередь, очередь может быть слишком большой, и память может быть переполнена.
так что нам делать?
Это на самом деле зависит от конкретных потребностей.Если подробный журнал входа в систему для вас не так важен, то вы можете поставить журнал входа в другую очередь через Thread.UncaughtExceptionHandler, а затем регулярно сливать данные, потому что количество входов в систему почти каждой системы есть статистика.
Если вы хотите не только подсчитать количество входов в систему, но и узнать, кто вошел в систему, вы можете заранее подготовить резервную базу данных и поместить данные в другую очередь для записи в резервную базу данных, когда основная база данных перегружена. .
На самом деле, Thread.UncaughtExceptionHandler — не лучший способ справиться с этой ситуацией, и JDK не рекомендует создавать показанные нами потоки, в большинстве случаев лучше использовать пул потоков. Однако основное внимание в этой статье уделяется представлению Thread.UncaughtExceptionHandler и углублению памяти.
Кроме того, этот асинхронный режим, основанный на очереди памяти JDK, вызовет большую область потери сообщений при зависании службы.Если читатель действительно сталкивается с таким сценарием, а объем ваших пользователей значительно увеличивается с каждым днем, это рекомендуется использовать его непосредственно MQ. Использование очередей памяти рекомендуется только тогда, когда количество пользователей стабильно и вы можете гарантировать, что пиковая нагрузка на систему никогда не превысит определенного расчетного значения.
Связанный код
Давайте напишем код для реализации описанного выше сценария.
Во-первых, давайте создадим класс сущности, представляющий журнал входа в систему.
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 登录日志对象,用于记录用户登录信息
* @author zhuzh
* @date 2019.10.17
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginLogPO {
private int id;
private String username;
private String createTime;
}
С классом сущности продолжайте писать интерфейс уровня DAO.
/**
* 登录日志持久层
* @author zhuzh
* @date 2019.10.17
*/
public interface LoginLoggingDAO {
/**
* 记录登录日志到数据库
* @param po
* @return
*/
boolean log(LoginLogPO po);
/**
* 记录日志失败
* @return
*/
void logFailed();
/**
* 获取备用数据源中的日志数据量
* @return
*/
int getBackupDatasourceSize();
}
Класс реализации симуляции слоя DAO, мы фактически не будем вставлять базу данных, а будем ставить данные в очередь
/**
* 一个登录日志持久层的模拟实现类
* @author zhuzh
* @date 2019.10.17
*/
public class MockLoginLoggingDAOImpl implements LoginLoggingDAO {
/**
* 使用定长队列来模拟数据库连接池资源紧张的场景
* MAIN_DATASOURCE代表主数据源
* 创建一个长度为5的队列,队列满了之后,向队列插入数据会阻塞,直到队列有空闲位置才可插入。
*/
private static final ArrayBlockingQueue<LoginLogPO> MAIN_DATASOURCE = new ArrayBlockingQueue<>(5);
/**
* 类初始化时直接将MAIN_DATASOURCE塞满,这样线程再插入数据时就会抛出异常
*/
static {
System.out.println("第三步:将主数据源塞满,模拟主数据源连接池资源不足");
for (int i = 0; i < 5; i++) {
MAIN_DATASOURCE.offer(new LoginLogPO(i,"用户"+i,"2019-10-1"+i));
}
}
/**
* BACKUP_DATASOURCE代表备用数据源,主数据源不可用时程序会切换到备用数据源,长度为100
*/
private static final ArrayBlockingQueue<LoginLogPO> BACKUP_DATASOURCE = new ArrayBlockingQueue<>(100);
private static final AtomicInteger FAILED_COUNTER= new AtomicInteger();
private static AtomicBoolean USE_BACKUP = new AtomicBoolean();
@Override
public boolean log(LoginLogPO po) {
ArrayBlockingQueue<LoginLogPO> currentDatasource = getDatasource();
try {
boolean success = currentDatasource.offer(po,3, TimeUnit.SECONDS);
if (!success){
throw new DatasourceBusyException("主数据源繁忙,即将切换备用数据源!");
}
System.out.println("第五步:入库成功,入库成功的数据源为,currentDatasource="+(currentDatasource==BACKUP_DATASOURCE?"BACKUP_DATASOURCE":"MAIN_DATASOURCE"));
}catch (InterruptedException e){
e.printStackTrace();
return false;
}
return true;
}
@Override
public void logFailed() {
//记录日志的失败次数+1
int currentFailedCount = FAILED_COUNTER.incrementAndGet();
//如果已经失败10次了,说明现在主数据源状态不是很理想啊
if (currentFailedCount>=10&&!USE_BACKUP.get()){
//就启用备用数据源
USE_BACKUP.compareAndSet(false,true);
System.out.println("主数据源繁忙,已切换数据源为备数据源,当前记录失败次数="+currentFailedCount+",备用数据源标志位="+USE_BACKUP.get()+",将使用备用数据源");
}
}
/**
* 获取数据源
* @return
*/
private ArrayBlockingQueue<LoginLogPO> getDatasource(){
//如果主数据源队列未满,说明主数据库连接池资源充足,可以切回主数据源
if (MAIN_DATASOURCE.isEmpty()){
FAILED_COUNTER.set(0);
USE_BACKUP.compareAndSet(true,false);
}
if (USE_BACKUP.get()){
return BACKUP_DATASOURCE;
}
return MAIN_DATASOURCE;
}
@Override
public int getBackupDatasourceSize() {
return BACKUP_DATASOURCE.size();
}
}
Слой персистентности написан, нам также нужно написать Bean factory для получения singleton LoginLoggingDAO через статические методы
/**
* Bean工厂,用于获取日志记录的DAO
* @author zhuzh
* @date 2019.10.17
*/
public class LoginLogBeanFactory {
private LoginLogBeanFactory(){}
private static final Object locker = new Object();
private static LoginLoggingDAO loginLoggingDAO;
public static LoginLoggingDAO getInstance(){
if (loginLoggingDAO == null){
synchronized (locker){
loginLoggingDAO = new MockLoginLoggingDAOImpl();
}
}
return loginLoggingDAO;
}
}
Когда главное время соединения источника данных, мы бросаем исключение, чтобы сделать определенный маркер для легкой идентификации текущего обработчика состояния
/**
* 使用特定异常标明当前确实是数据源的问题
* @author zhuzh
* @date 2019.10.17
*/
public class DatasourceBusyException extends RuntimeException {
public DatasourceBusyException(String message) {
super(message);
}
}
Задачи выполнения потока
/**
* 记录登录日志的执行任务
* @author zhuzh
* @date 2019.10.17
*/
public class LoginLoggingTask implements Runnable {
private LoginLogPO po;
public LoginLoggingTask(LoginLogPO po1){
po = po1;
}
@Override
public void run() {
LoginLoggingDAO dao = LoginLogBeanFactory.getInstance();
dao.log(po);
}
@Override
public String toString() {
return "LoginLoggingTask{" +
"po=" + po +
'}';
}
}
пользовательская нить
/**
* 一个用来异步记录登录日志的线程
* @author zhuzh
* @date 2019.10.17
*/
public class LoginLoggerThread extends Thread {
/**
* 执行任务暂存
*/
private Runnable task;
/**
* 此方法将runnable执行任务赋值给类的成员变量task,便于后续如果出现异常时可以重新执行任务
* @param target
*/
public LoginLoggerThread(Runnable target) {
super(target);
task = target;
}
public Runnable getTask() {
return task;
}
}
и UncaughtExceptionHandler
/**
* 一个自定义的线程任务异常处理器
* @author zhuzh
* @date 2019.10.17
*/
public class LoginLoggingUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
//如果确实是数据源的问题,我们再切换数据源,如果抛出了别的异常,暂不处理
if (e instanceof DatasourceBusyException){
LoginLoggingDAO dao = LoginLogBeanFactory.getInstance();
dao.logFailed();
LoginLoggerThread thread = (LoginLoggerThread)t;
Runnable task = thread.getTask();
System.out.println("第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task="+task.toString());
AsynLogger.log(task);
}
}
}
Затем мы пишем класс асинхронной обработки журнала для получения записей журнала в очередь буфера.
/**
* 一个登陆日志异步处理类
* @author zhuzh
* @date 2019.10.17
*/
public class AsynLogger {
/**
* 日志缓冲队列,当用户登录成功时,通过调用AsynLogger.log将日志放入缓冲队列
*/
private static final ArrayBlockingQueue<Runnable> TASKS = new ArrayBlockingQueue<>(50);
/**
* 初始化50个用户登录日志
*/
static {
System.out.println("第一步:初始化50条登录日志数据放入[日志缓冲队列TASKS]");
for (int i = 0; i < 50; i++) {
TASKS.offer(new LoginLoggingTask(new LoginLogPO(i,"用户"+i,"2019-10-1"+i)));
}
}
public static void log(Runnable loggingTask){
TASKS.offer(loggingTask);
}
/**
* 调用startLogging方法启动一个线程轮训缓冲队列
*/
public static void startLogging(){
Thread a = new Thread(()->{
while (true){
Runnable task = TASKS.poll();
if (task==null){
continue;
}
LoginLoggerThread thread = new LoginLoggerThread(task);
thread.setUncaughtExceptionHandler(new LoginLoggingUncaughtExceptionHandler());
thread.start();
}});
a.setDaemon(true);
a.start();
System.out.println("第二步:创建独立守护线程轮训[日志缓冲队列TASKS],如果有数据,创建一个线程去处理,并且设置UncaughtExceptionHandler");
}
}
Наконец, мы напишем еще один основной метод, сделайте это!
/**
* 测试功能主类
* @author zhuzh
* @date 2019.10.17
*/
public class UncaughtExceptionHandlerExample {
public static void main(String[] args){
AsynLogger.startLogging();
try {
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("第六步:所有任务执行完毕,备用数据源有"+ LoginLogBeanFactory.getInstance().getBackupDatasourceSize()+"条数据");
}
}
выходной результат
第一步:初始化50条登录日志数据放入[日志缓冲队列TASKS]
第二步:创建独立守护线程轮训[日志缓冲队列TASKS],如果有数据,创建一个线程去处理,并且设置UncaughtExceptionHandler
第三步:将主数据源塞满,模拟主数据源连接池资源不足
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=33, username=用户33, createTime=2019-10-133)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=38, username=用户38, createTime=2019-10-138)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=5, username=用户5, createTime=2019-10-15)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=39, username=用户39, createTime=2019-10-139)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=34, username=用户34, createTime=2019-10-134)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=36, username=用户36, createTime=2019-10-136)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=47, username=用户47, createTime=2019-10-147)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=37, username=用户37, createTime=2019-10-137)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=8, username=用户8, createTime=2019-10-18)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=46, username=用户46, createTime=2019-10-146)}
主数据源繁忙,已切换数据源为备数据源,当前记录失败次数=10,备用数据源标志位=true,将使用备用数据源
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=23, username=用户23, createTime=2019-10-123)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=40, username=用户40, createTime=2019-10-140)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=13, username=用户13, createTime=2019-10-113)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=17, username=用户17, createTime=2019-10-117)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=45, username=用户45, createTime=2019-10-145)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=15, username=用户15, createTime=2019-10-115)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=44, username=用户44, createTime=2019-10-144)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=12, username=用户12, createTime=2019-10-112)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=31, username=用户31, createTime=2019-10-131)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=18, username=用户18, createTime=2019-10-118)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=35, username=用户35, createTime=2019-10-135)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=43, username=用户43, createTime=2019-10-143)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=29, username=用户29, createTime=2019-10-129)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=30, username=用户30, createTime=2019-10-130)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=19, username=用户19, createTime=2019-10-119)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=16, username=用户16, createTime=2019-10-116)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=7, username=用户7, createTime=2019-10-17)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=27, username=用户27, createTime=2019-10-127)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=11, username=用户11, createTime=2019-10-111)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=6, username=用户6, createTime=2019-10-16)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=4, username=用户4, createTime=2019-10-14)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=28, username=用户28, createTime=2019-10-128)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=3, username=用户3, createTime=2019-10-13)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=20, username=用户20, createTime=2019-10-120)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=2, username=用户2, createTime=2019-10-12)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=41, username=用户41, createTime=2019-10-141)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=9, username=用户9, createTime=2019-10-19)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=22, username=用户22, createTime=2019-10-122)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=1, username=用户1, createTime=2019-10-11)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=0, username=用户0, createTime=2019-10-10)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=25, username=用户25, createTime=2019-10-125)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=42, username=用户42, createTime=2019-10-142)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=48, username=用户48, createTime=2019-10-148)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=14, username=用户14, createTime=2019-10-114)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=49, username=用户49, createTime=2019-10-149)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=26, username=用户26, createTime=2019-10-126)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=24, username=用户24, createTime=2019-10-124)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=21, username=用户21, createTime=2019-10-121)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=10, username=用户10, createTime=2019-10-110)}
第四步:线程执行异常,进入UncaughtExceptionHandler,任务重新丢回任务队列,task=LoginLoggingTask{po=LoginLogPO(id=32, username=用户32, createTime=2019-10-132)}
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第五步:入库成功,入库成功的数据源为,currentDatasource=BACKUP_DATASOURCE
第六步:所有任务执行完毕,备用数据源有50条数据
Process finished with exit code 0
На самом деле такой способ запуска потока для единовременной обработки задач неверен.следует использовать пул потоков, но основная цель этой статьи — объяснить внутренний интерфейс UncaughtExceptionHandler Thread, и позже я изменю этот пример, добавив лучшее решение.
Исходный код этой статьи находится на github, вы можете перейти наСкачать здесь