如何系列 如何实现主线程等待子线程超时(多种解决方案)

背景

在某些情况下,我们希望执行一个任务,但不能无限期地等待其完成,而是需要在一定的时间范围内获取任务的结果或决定任务超时并进行相应的处理。这种场景通常用于处理任务执行的时限,避免任务长时间阻塞主线程或无限期地等待结果。

方案

  • Thread.join(long millis)
  • Object.wait()和notify() + 锁
  • FutureTask
  • Future + ExecutorService
  • Semaphore
  • CountDownLatch
  • CompletableFuture
  • Guava SimpleTimeLimiter

Thread.join(long millis)

如果任务是通过启动一个线程来执行的,可以使用join(long millis)方法让主线程等待指定的时间,然后继续执行其他操作。join(long millis)方法的参数表示最多等待的时间(毫秒),如果在该时间内任务线程没有执行完毕,主线程会继续执行。示例代码如下:

public class TimeoutExample {
    
    
    public static void main(String[] args) {
    
    
        // 启动任务线程
        Thread taskThread = new Thread(new Task());
        taskThread.start();

        try {
    
    
            // 等待任务执行结果,最多等待5秒 block
            taskThread.join(5000);
            if (taskThread.isAlive()) {
    
    
                System.out.println("任务执行超时");
                // 可以考虑中断任务线程:taskThread.interrupt();
            } else {
    
    
                System.out.println("任务执行完成");
            }
        } catch (InterruptedException e) {
    
    
            System.out.println("主线程被中断");
        }
    }
}

class Task implements Runnable {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            // 模拟耗时任务
            Thread.sleep(3000);
            System.out.println("任务完成");
        } catch (InterruptedException e) {
    
    
            System.out.println("任务被中断");
        }
    }
}

Object.wait()和notify() + 锁

这是一种传统的线程同步方法,可以通过wait()notify()方法在主线程中等待任务完成,并在超时时间内唤醒主线程。示例代码如下:

注意必须配合LOCK

public class TimeoutExample {
    
    
    public static void main(String[] args) {
    
    
        Object lock = new Object();

        // 启动任务线程
        new Thread(new Task(lock)).start();

        synchronized (lock) {
    
     // block
            try {
    
    
                // 等待任务执行结果,最多等待5秒
                lock.wait(5000);
                System.out.println("任务执行完成");
            } catch (InterruptedException e) {
    
    
                System.out.println("主线程被中断");
            }
        }
    }
}

class Task implements Runnable {
    
    
    private final Object lock;

    public Task(Object lock) {
    
    
        this.lock = lock;
    }

    @Override
    public void run() {
    
    
        synchronized (lock) {
    
    
            try {
    
    
                // 模拟耗时任务
                Thread.sleep(3000);
                System.out.println("任务完成");
                // 任务完成时通知主线程
                lock.notify();
            } catch (InterruptedException e) {
    
    
                System.out.println("任务被中断");
            }
        }
    }
}

FutureTask

基于Java的FutureTaskThread的超时等待方案。这种方法可以手动创建FutureTask对象并通过Thread来执行任务,然后使用get(long timeout, TimeUnit unit)方法来设置最大等待时间。

public class TimeoutExample {
    
    
    public static void main(String[] args) {
    
    
        Callable<String> task = () -> {
    
    
            // 模拟耗时任务
            Thread.sleep(3000);
            return "任务完成";
        };

        // 创建FutureTask对象
        FutureTask<String> futureTask = new FutureTask<>(task);

        // 创建并启动Thread执行FutureTask
        Thread taskThread = new Thread(futureTask);
        taskThread.start();

        try {
    
    
            // 等待任务执行结果,最多等待5秒 block
            String result = futureTask.get(5, TimeUnit.SECONDS);
            System.out.println("任务执行结果:" + result);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
    
    
            System.out.println("任务执行超时或出现异常:" + e.getMessage());
            // 如果任务在5秒内没有完成,可以中断任务线程
            taskThread.interrupt();
        }
    }
}

Future + ExecutorService

可以使用ExecutorServiceFuture来实现超时机制。这样,主线程可以等待任务的结果,但如果任务在规定的时间内没有完成,主线程会继续执行其他操作。

下面是一个示例代码,演示了如何在主线程等待任务的执行结果,但最多等待5秒钟:

public class TimeoutExample {
    
    
    public static void main(String[] args) {
    
    
        // 创建线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 提交任务并获取Future对象
        Future<String> future = executorService.submit(new Task());

        try {
    
    
            // 等待任务执行结果,最多等待5秒 block
            String result = future.get(5, TimeUnit.SECONDS);
            System.out.println("任务执行结果:" + result);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
    
    
            // 超时或出现异常时的处理
            System.out.println("任务执行超时或出现异常:" + e.getMessage());
        } finally {
    
    
            // 关闭线程池
            executorService.shutdown();
        }
    }
}

class Task implements Callable<String> {
    
    
    @Override
    public String call() throws Exception {
    
    
        // 模拟耗时任务
        Thread.sleep(3000);
        return "任务完成";
    }
}

在上面的代码中,我们创建了一个ExecutorService线程池,并提交了一个Task任务。通过Future.get(long timeout, TimeUnit unit)方法,我们等待任务的执行结果,最多等待5秒钟。如果任务在规定时间内完成,就可以得到任务的结果;如果任务在5秒内没有完成,将抛出TimeoutException异常,从而使得主线程可以继续执行其他操作。注意,任务的实际执行时间是3秒,因此在5秒的超时时间内,任务可以正常完成。

Semaphore

Semaphore是另一种同步辅助类,它可以控制同时访问特定资源的线程数量。我们可以将Semaphore作为信号量来实现主线程等待任务完成的超时机制。示例代码如下:

public class TimeoutExample {
    
    
    public static void main(String[] args) {
    
    
        // 创建Semaphore,并指定许可数量为0
        Semaphore semaphore = new Semaphore(0);

        // 启动任务线程
        new Thread(new Task(semaphore)).start();

        try {
    
    
            // 等待任务执行结果,最多等待5秒 block
            if (semaphore.tryAcquire(1, 5, TimeUnit.SECONDS)) {
    
    
                System.out.println("任务执行完成");
            } else {
    
    
                System.out.println("任务执行超时");
            }
        } catch (InterruptedException e) {
    
    
            System.out.println("主线程被中断");
        }
    }
}

class Task implements Runnable {
    
    
    private final Semaphore semaphore;

    public Task(Semaphore semaphore) {
    
    
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
    
    
        try {
    
    
            // 模拟耗时任务
            Thread.sleep(3000);
            System.out.println("任务完成");
            // 任务完成时释放许可
            semaphore.release();
        } catch (InterruptedException e) {
    
    
            System.out.println("任务被中断");
        }
    }
}

CountDownLatch

CountDownLatch是一个同步辅助类,可以用于等待一个或多个线程执行完成。主线程可以通过await(long timeout, TimeUnit unit)方法等待特定时间内的任务完成,或者在任务完成时调用countDown()方法通知主线程。示例代码如下:

public class TimeoutExample {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 创建CountDownLatch,并指定计数器为1
        CountDownLatch latch = new CountDownLatch(1);

        // 启动任务线程
        new Thread(new Task(latch)).start();

        // 等待任务执行结果,最多等待5秒 block
        if (latch.await(5, TimeUnit.SECONDS)) {
    
    
            System.out.println("任务执行完成");
        } else {
    
    
            System.out.println("任务执行超时");
        }
    }
}

class Task implements Runnable {
    
    
    private final CountDownLatch latch;

    public Task(CountDownLatch latch) {
    
    
        this.latch = latch;
    }

    @Override
    public void run() {
    
    
        try {
    
    
            // 模拟耗时任务
            Thread.sleep(3000);
            System.out.println("任务完成");
        } catch (InterruptedException e) {
    
    
            System.out.println("任务被中断");
        } finally {
    
    
            // 任务完成时通知主线程
            latch.countDown();
        }
    }
}

CompletableFuture

CompletableFuture是Java 8引入的一个强大的异步编程工具。通过CompletableFuture,可以方便地实现异步任务并等待其完成,并使用CompletableFuture.get(long timeout, TimeUnit unit)来实现超时等待。示例代码如下:

public class TimeoutExample {
    
    
    public static void main(String[] args) {
    
    
        // 启动异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    
    
            try {
    
    
                // 模拟耗时任务
                Thread.sleep(3000);
                return "任务完成";
            } catch (InterruptedException e) {
    
    
                return "任务被中断";
            }
        });

        try {
    
    
            // 等待任务执行结果,最多等待5秒 block
            String result = future.get(5, TimeUnit.SECONDS);
            System.out.println(result);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
    
    
            System.out.println("任务执行超时或出现异常:" + e.getMessage());
        }
    }
}

Guava SimpleTimeLimiter

Guava库(Google Guava)提供了一个名为SimpleTimeLimiter的类,用于设置和执行带有超时限制的方法调用。这个类可以在Java方法上设置最大执行时间,如果方法在规定时间内没有完成,将抛出UncheckedTimeoutException异常。以下是使用Guava的SimpleTimeLimiter的示例代码:

import com.google.common.util.concurrent.SimpleTimeLimiter;
import com.google.common.util.concurrent.UncheckedTimeoutException;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TimeoutExample {
    
    
    public static void main(String[] args) {
    
    
        // 创建ExecutorService线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 创建SimpleTimeLimiter对象
        SimpleTimeLimiter timeLimiter = SimpleTimeLimiter.create(executorService);

        // 定义一个耗时任务
        Callable<String> task = () -> {
    
    
            // 模拟耗时任务
            Thread.sleep(3000);
            return "任务完成";
        };

        try {
    
    
            // 使用SimpleTimeLimiter执行任务,最多等待5秒 block
            String result = timeLimiter.callWithTimeout(task, 5, TimeUnit.SECONDS);
            System.out.println("任务执行结果:" + result);
        } catch (UncheckedTimeoutException e) {
    
    
            System.out.println("任务执行超时:" + e.getMessage());
        } catch (Exception e) {
    
    
            System.out.println("任务执行出现异常:" + e.getMessage());
        } finally {
    
    
            // 关闭线程池
            executorService.shutdown();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/abu935009066/article/details/132096779