diff --git a/src/main/java/com/imyeyu/java/thread/AsyncRetryExecutor.java b/src/main/java/com/imyeyu/java/thread/AsyncRetryExecutor.java new file mode 100644 index 0000000..01e8200 --- /dev/null +++ b/src/main/java/com/imyeyu/java/thread/AsyncRetryExecutor.java @@ -0,0 +1,148 @@ +package com.imyeyu.java.thread; + +import com.imyeyu.java.bean.Callback; +import com.imyeyu.java.bean.CallbackArg; + +import java.util.concurrent.TimeUnit; + +/** + * + * @author 夜雨 + * @since 2025-11-06 17:45 + */ +public class AsyncRetryExecutor { + + /** 默认重试次数,-1 表无限 */ + private static final int DEFAULT_MAX_RETRY = -1; + + /** 默认重试间隔(毫秒) */ + private static final long DEFAULT_RETRY_INTERVAL = -1; + + /** 线程名前缀 */ + private static final String DEFAULT_THREAD_NAME_PREFIX = "AsyncRetryThread-"; + + /** true 为守护进程 */ + private static final boolean DEFAULT_DAEMON = false; + + private final Thread thread; + private volatile boolean isSuccess = false; + private volatile boolean isRunning = false; + + private AsyncRetryExecutor(Builder builder) { + thread = new Thread(() -> { + int retryCount = 0; + while (isRunning && !isSuccess && (builder.maxRetry < 0 || retryCount <= builder.maxRetry)) { + try { + builder.callback.handler(); + isSuccess = true; + } catch (Exception e) { + retryCount++; + if (!isRunning) { + // 中断 + break; + } + if (0 < builder.maxRetry && builder.maxRetry < retryCount) { + // 超过重试次数 + builder.onRetryExhausted.handler(e); + break; + } + // 重试 + try { + TimeUnit.MILLISECONDS.sleep(builder.retryInterval); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + break; + } + } + } + }, builder.threadNamePrefix + Thread.currentThread().threadId()); + thread.setDaemon(builder.daemon); + } + + /** + * 构造器 + * + * @author 夜雨 + * @since 2025-11-06 23:37 + */ + public static class Builder { + + // 必需参数 + private final Callback callback; + + // 可选参数(带默认值) + private int maxRetry = DEFAULT_MAX_RETRY; + private long retryInterval = DEFAULT_RETRY_INTERVAL; + private String threadNamePrefix = DEFAULT_THREAD_NAME_PREFIX; + private boolean daemon = DEFAULT_DAEMON; + private CallbackArg onRetryExhausted; + + public Builder(Callback callback) { + this.callback = callback; + } + + public Builder maxRetry(int maxRetry) { + this.maxRetry = maxRetry; + return this; + } + + public Builder retryInterval(long retryInterval) { + this.retryInterval = retryInterval; + return this; + } + + public Builder threadNamePrefix(String prefix) { + this.threadNamePrefix = prefix; + return this; + } + + public Builder daemon(boolean daemon) { + this.daemon = daemon; + return this; + } + + public Builder onRetryExhausted(CallbackArg onRetryExhausted) { + this.onRetryExhausted = onRetryExhausted; + return this; + } + + public AsyncRetryExecutor build() { + return new AsyncRetryExecutor(this); + } + } + + public static AsyncRetryExecutor create(Callback callback) { + return new Builder(callback).build(); + } + + public static AsyncRetryExecutor create(Callback callback, int maxRetry) { + return new Builder(callback).maxRetry(maxRetry).build(); + } + + public static AsyncRetryExecutor create(Callback callback, long interval) { + return new Builder(callback).retryInterval(interval).build(); + } + + public void start() { + if (!isRunning) { + isRunning = true; + isSuccess = false; + thread.start(); + } + } + + public void interrupt() { + isRunning = false; + if (thread.isAlive() && !thread.isInterrupted()) { + thread.interrupt(); + } + } + + public boolean isSuccess() { + return isSuccess; + } + + public boolean isRunning() { + return isRunning; + } +} diff --git a/src/test/java/test/TestThread.java b/src/test/java/test/TestThread.java new file mode 100644 index 0000000..8199710 --- /dev/null +++ b/src/test/java/test/TestThread.java @@ -0,0 +1,35 @@ +package test; + +import com.imyeyu.java.thread.AsyncRetryExecutor; +import org.junit.jupiter.api.Test; + +/** + * @author 夜雨 + * @since 2025-11-06 23:59 + */ +public class TestThread { + + @Test + public void testAsyncRetryExecutor() throws InterruptedException { + Object lock = new Object(); + AsyncRetryExecutor executor = AsyncRetryExecutor.create(() -> { + double value = Math.random(); + if (.9 < value) { + System.out.println("success " + value); + synchronized (lock) { + lock.notifyAll(); + } + } else { + System.out.println("fail " + value); + throw new RuntimeException("fail"); + } + }); + executor.start(); + + synchronized (lock) { + lock.wait(); + } + + assert executor.isSuccess(); + } +} \ No newline at end of file