多线程处理的结果顺序完成和结束

多线程处理的结果顺序完成和结束

注意:本文章讲的是多线程如何顺序结束,不是多线程如何顺序执行!

使用场景:

主线程每次收到一批数据处理后,需要对数据进行处理,数据长度和大小不定,有的几毫秒就处理好了,有的几秒钟才能处理完。如果采用单线程,耗时过长肯定是无法接受的,所以选择使用多线程。

但是!每一批数据处理完成后,还需要按照接收到数据的顺序,将处理的数据发送出去,所以每一个数据发送完成前还要判断前一个数据是否发送完成。

真的是···很让人头痛。

比如:我们有一个List,现在需要for循环处理这个List,每一个String对象我们都开启一个线程去处理它,最后我们需要将处理的结果按for循环的顺序发送到其他地方。

解决方案:

一、常用方案

等待所有线程全部结束后,按顺序发送。

理论上来说:这不算是多线程的顺序结束,算是最终结果的顺序执行。即我们使用CountDownLatch或者CompletableFuture来阻塞线程,等待所有的线程或者异步任务处理完成后,将结果进行for循环处理

需要注意的是:使用这种方式获取到的结果List顺序不一定是按照for循环的顺序,需要加标识进行排序处理。

这种方法实现比较简单,但是处理时间取决于当前批次数据的最长数据处理时间。比如当前批次数据某个线程处理长达10秒,那么所有线程处理完的时间就是10秒+for循环发送时间,比for循环单线程处理快是肯定。

二、嵌套方案(效率优于常用方案)

因为处理数据部分是不要求顺序的,只有在发送时才要求顺序发送。那么我想是不是有一种方案,可以先让所有线程进行数据处理,然后当数据处理部分完成后,卡在发送部分前面。因为是for循环执行的线程,所以每一个线程都可以设置前一个线程作为自己的参数,以此实现嵌套效果,当前线程的前一个线程执行完成后,当前线程才会进行发送处理,以此类推,实现数据处理部分多线程处理,发送部分顺序结束。

代码核心要素:前一个线程作为当前现成的参数、Lock、Condition。

以下是示例代码:

    public void test1(List<String> list) throws Exception {
    
    
        ExecutorService executor = Executors.newFixedThreadPool(10);
        List<SendTask> sendTaskList = new ArrayList<>();
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        for (int i = 0; i < list.size(); ++i) {
    
    
            String str = list.get(i);
            if (i == 0) {
    
    
            //第一个的前一个线程设置为null
                sendTaskList.add(new SendTask(str, null, lock, condition));
            } else {
    
    
            //第二次开始,参数为前一个线程
                sendTaskList.add(new SendTask(str, sendTaskList.get(sendTaskList.size() - 1), lock, condition));
            }
        }
		executor.submit(sendTask);
    }

线程处理代码:

public class SendTask implements Runnable {
    
    
    private final String message;
    private SendTask previousTask;
    private Lock lock;
    private Condition condition;
    private volatile boolean isDone;


    public SendTask(String message, SendTask previousTask, Lock lock, Condition condition) {
    
    
        this.message = message;
        //这里接收上一个线程
        this.previousTask = previousTask;
        this.lock = lock;
        this.condition = condition;
    }


    private void send() {
    
    
        // 在这里编写发送逻辑
        System.out.println(message + "开始发送");
        Integer time = 500;
        try {
    
    
            Thread.sleep(time);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
//        System.out.println(message + "发送时间" + time);
        System.out.println(message + " 发送完成!");
    }

    @Override
    public void run() {
    
    
        try {
    
    
//            System.out.printf("%s 开始处理", message);
            Integer time = (new Random().nextInt(3) + 1) * 1000;
            Thread.sleep(time);
//            System.out.println(message + "处理时间" + time);
            lock.lock();
            //如果不是第一个线程,并且前一个线程未执行完成,则等待
            while (previousTask != null && !previousTask.isDone()) {
    
    
                condition.await();
            }
            send();
            isDone = true;
            //提醒可以抢锁了
            condition.signalAll();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
        	//解锁
            lock.unlock();
        }
    }

    public boolean isDone() {
    
    
        return isDone;
    }

这种方案,就我自己的测试结果来说,是比普通方案要快的,并且顺序是能够得到保障的。但是需要注意的是,Conditon.await()和Lock可能会存在死锁的情况,真实业务代码里需要考虑到这方面,最好设置一个超时时间,避免死锁的发生。

猜你喜欢

转载自blog.csdn.net/python_small_pan/article/details/130768069