【Java并发】线程池多线程调用 限定超时时间的future.get()串行阻塞式运行

在这里插入图片描述

前言

在并发编程中经常用非阻塞模型,不论是继承thread类,还是实现runnable接口,都无法保证获取到之前的执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果。

Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。

java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)方法可以在获取执行结果同时设置一个超时时间,如果超时当前线程会因为收到 TimeoutException 而被中断,线程池里继续执行。但是future的get()方法会堵塞当前线程的执行。

本文通过线程池多线程任务调用,串行阻塞式运行,虽然慢但稳妥,通过get()设置接口超时时间、要求单个线程运行时必须要有超时时间,超时则放弃,并运行下一个任务。

这里的实现仅能用,暂时不完善。

在这里插入图片描述

实现

拆解

我们假设要执行的任务work为:将某个UUID添加进list中,并在任务重打印当前执行的线程名字以区别,某个情况时,接口调用时间延长为2200ms,使接口调用超时,如下:

    /**
     * 要执行的任务 work
     * @param list
     * @return
     */
    public static List<String> work(List<String> list, String s) throws Exception {
        // 模拟某个情况时,接口调用时间延长为2200ms,使接口调用超时
        if (s.equals("BBB")) {
            Thread.sleep(1100);
        }
        list.add(UUID.randomUUID().toString() + " " + s);
        System.out.println("当前执行线程名 : " + Thread.currentThread().getName());
        return list;
    }

我们初始化线程池并定义线程池参数和策略,这里我们给定两个线程。

后创建线程,多线程执行 work 任务:定义线程执行的任务work,并将其放入线程池中执行,并通过get()设置线程调用超时时间,执行完之后关闭手动线程池,并返回结果:

    /**
     * 多线程执行 work 任务
     * 轮询阻塞式执行
     * @param list
     * @return List<String>
     * @throws InterruptedException
     */
    public static List<String> getExecutorService1(List<String> list) throws InterruptedException{
        System.out.println("=========== 开始执行多线程 ==========");
        // 线程池只给2个线程
        ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0L,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

        // 循环取出notAllowedKeyWords中的词记为notAllowedKeyWord,通过线程池分配的线程,调用work接口,把单个notAllowedKeyWord放入list中
        for (String notAllowedKeyWord : notAllowedKeyWords) {
            // 中间变量tempList
            System.out.println("线程准备中...");
            FutureTask<List<String>> future = new FutureTask<> (
                    new Callable<List<String>>() {
                        @Override
                        public List<String> call() throws Exception {
                            return work(list, notAllowedKeyWord);
                        }
                    });

            // 加载future
            executorService.execute(future);
            try {
                // 线程池线程执行设置超时时间
                future.get(1, TimeUnit.SECONDS);
            } catch (Exception e) {
                // 超时报个错
                e.printStackTrace();
                // 超时则放弃这个子线程的任务,让其他线程继续往下执行
                future.cancel(Boolean.TRUE);
            }
        }
        // 执行完,关闭线程池
        executorService.shutdown();
        // 执行完,返回结果
        return list;
    }

ExecutorService 实现多线程执行任务的方法:

  1. 方法名为 getExecutorService,接收一个 List 类型的参数 list,返回一个 List 类型的结果,并可能抛出 InterruptedException 异常。
  2. 初始化线程池:
    创建一个 ThreadPoolExecutor 实例,参数分别为核心线程数(2)、最大线程数(2)、空闲线程存活时间(0 秒)、时间单位(毫秒)、以及一个无界阻塞队列 LinkedBlockingQueue 作为工作队列。
  3. 循环处理任务:
    遍历 notAllowedKeyWords 列表中的每个元素 notAllowedKeyWord。
  4. 创建 FutureTask:
    对于每个 notAllowedKeyWord,创建一个新的 FutureTask<List> 实例,其中的 Callable 实现类在 call() 方法中调用 work(list, notAllowedKeyWord) 方法。
  5. 提交任务到线程池:
    使用 executorService.execute(future) 将 FutureTask 提交到线程池中执行。
  6. 获取结果并设置超时:
    使用 future.get(1, TimeUnit.SECONDS) 获取 FutureTask 的结果,并设置超时时间为 1 秒。
  7. 处理超时异常:
    如果在获取结果时发生超时异常,则捕获该异常,打印堆栈信息,并取消该 FutureTask。
  8. 关闭线程池:
    在所有任务执行完毕后,调用 executorService.shutdown() 关闭线程池。
  9. 返回结果:最后返回处理后的 list 结果。

notAllowedKeyWords 列表中的每个元素,并将结果添加到 list 中。线程池限制了同时执行的任务数量为 2,如果某个任务执行超过 1 秒还未完成,则取消该任务并让其他任务继续执行。最后,关闭线程池并返回处理后的 list 结果。

Main.java

Main.java

完成代码脚本如下,我们定义了一个key字符串,将中的字符串字符拆解并放入notAllowedKeyWords 中,随后让线程获取notAllowedKeyWords 中的单词,执行work任务:

import java.util.*;
import java.util.concurrent.*;

public class Main {

    // 20个
    private static String key = "AAA|BBB|CCC|DDD|EEE|FFF|GGG|HHH|III|JJJ|KKK|LLL|MMM|NNN|OOO|PPP|QQQ|RRR|SSS|TTT";

    private static List<String> notAllowedKeyWords = new ArrayList<>(0);

    private static List<String> res = new ArrayList<>();

    static {
        String keyStr[] = key.split("\\|");
        for (String str : keyStr) {
            notAllowedKeyWords.add(str);
        }
    }

    /**
     * 要执行的任务 work
     * @param list
     * @return
     */
    public static List<String> work(List<String> list, String s) throws Exception {
        // 模拟某个情况时,接口调用时间延长为2200ms,使接口调用超时
        if (s.equals("BBB")) {
            Thread.sleep(1100);
        }
        list.add(UUID.randomUUID().toString() + " " + s);
        System.out.println("当前执行线程名 : " + Thread.currentThread().getName());
        return list;
    }


    /**
     * 多线程执行 work 任务
     * 轮询阻塞式执行
     * @param list
     * @return List<String>
     * @throws InterruptedException
     */
    public static List<String> getExecutorService1(List<String> list) throws InterruptedException{
        System.out.println("=========== 开始执行多线程 ==========");
        // 线程池只给2个线程
        ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0L,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

        // 循环取出notAllowedKeyWords中的词记为notAllowedKeyWord,通过线程池分配的线程,调用work接口,把单个notAllowedKeyWord放入list中
        for (String notAllowedKeyWord : notAllowedKeyWords) {
            // 中间变量tempList
            System.out.println("线程准备中...");
            FutureTask<List<String>> future = new FutureTask<> (
                    new Callable<List<String>>() {
                        @Override
                        public List<String> call() throws Exception {
                            return work(list, notAllowedKeyWord);
                        }
                    });

            // 加载future
            executorService.execute(future);
            try {
                // 线程池线程执行设置超时时间
                future.get(1, TimeUnit.SECONDS);
            } catch (Exception e) {
                // 超时报个错
                e.printStackTrace();
                // 超时则放弃这个子线程的任务,让其他线程继续往下执行
                future.cancel(Boolean.TRUE);
            }
        }
        // 执行完,关闭线程池
        executorService.shutdown();
        // 执行完,返回结果
        return list;
    }


    /**
     * 主程序
     * 任务目的是传入 List<String> res 到 getExecutorService 接口中,
     * getExecutorService 方法构造多线程,将多个 【randomUUID + " " + key中的各个单词】 组成元素,存入res中,最后返回res
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        System.out.println("notAllowedKeyWords: " + notAllowedKeyWords);
        System.out.println("notAllowedKeyWords.size(): " + notAllowedKeyWords.size());
        long startTime = System.currentTimeMillis();

        try {
            res = getExecutorService1(res);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 执行完,总时间打印
        System.out.println("========== submit总共cost 时间:" + (System.currentTimeMillis() - startTime)/1000 + "秒 ===========");
        System.out.println("res: " + res);
        System.out.println("res.size(): " + res.size());
    }
}

运行

运行结果,所有线程执行时打印所属线程号,可以看到是交替进行,最后list结果也打印出来。

在这里插入图片描述
可以看到最后虽然线程本应该是异步执行的,但是通过get()阻塞线程获取执行结果,因此变成串行执行。

可以看到如果存在线程超时,当前线程会因为收到 TimeoutException 而被中断,但是线程池里其它的线程却继续执行完毕。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
好的,以下是一个基于线程池多线程并发读取不同文件内容的Java代码案例: ```java import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FileReadThreadPool { private static final int THREAD_POOL_SIZE = 5; // 线程池大小 private ExecutorService executorService; // 线程池 private File[] files; // 需要读取的文件数组 public FileReadThreadPool(File[] files) { this.files = files; this.executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); } public void readFiles() { for (File file : files) { executorService.execute(new FileReaderRunnable(file)); } executorService.shutdown(); } private static class FileReaderRunnable implements Runnable { private File file; FileReaderRunnable(File file) { this.file = file; } @Override public void run() { try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line; while ((line = reader.readLine()) != null) { System.out.println(Thread.currentThread().getName() + " : " + line); } } catch (Exception e) { e.printStackTrace(); } } } } ``` 使用方法如下: ```java public static void main(String[] args) { File[] files = {new File("file1.txt"), new File("file2.txt"), new File("file3.txt")}; FileReadThreadPool fileReadThreadPool = new FileReadThreadPool(files); fileReadThreadPool.readFiles(); } ``` 在这个例子中,我们使用了一个固定大小的线程池(`newFixedThreadPool`),并将每个文件的读取任务放入线程池中执行。每个读取任务都是一个实现了`Runnable`接口的内部类`FileReaderRunnable`,它负责打开文件、读取文件内容并输出到控制台。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

锥栗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值