ThreadPool ExecutorService使用invokeAll提交多个任务并等待结果返回

https://blog.csdn.net/liangwenmail/article/details/79421029

 

 invokeAll 可以提交多个任务,在任务完成前该方法会阻塞,直到所有任务完成或中断或超时,返回Future列表。

package cn.t3; 
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
 
public class T5 {
    private static final ExecutorService exec=Executors.newCachedThreadPool();
    
    public static void main(String[] args) throws InterruptedException {
        Callable<String> c1 = new Callable<String>() {
 
            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"::正在执行计算");
                return "AA";
            }
        };
        Callable<String> c2 = new Callable<String>() {
            
            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"::正在执行计算");
                Thread.sleep(3000);
                return "BB";
            }
        };
        Callable<String> c3 = new Callable<String>() {
            
            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"::正在执行计算");
                Thread.sleep(1000);
                return "CC";
            }
        };
        
        List<Callable<String>> list = new ArrayList<Callable<String>>();
        list.add(c1);
        list.add(c2);
        list.add(c3);
        
        List<Future<String>> futures = exec.invokeAll(list, 2000, TimeUnit.MILLISECONDS); //阻塞方法,当所有任务执行完毕,中断或超时时返回。
        
        for(Future<String> future:futures){
            try {
                System.out.println(future.get());
            } catch (ExecutionException e) {
                System.out.println("异常");
            } catch (CancellationException e) {
                System.out.println("超时");
            }
        }
        
        exec.shutdown();
    }       
    
}   

 

pool-1-thread-2::正在执行计算
pool-1-thread-3::正在执行计算
AA
超时
CC

__________________________________________________________________________________________________________________________________________________

 

为何在多线程中,不能调用单例方法(一个简单的demo) 

https://blog.csdn.net/qq_43436117/article/details/125272412

多线程中,调用单例方法,虽然线程不会阻塞,但是数据也会不安全。

首先简单介绍CountDownLatch 的使用:同步工具类

(1) 概念
CountDownLatch可以使一个或多个线程等待其他线程各自执行完毕后再执行。

CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。

(2) 方法
CountDownLatch(int count):构造方法,创建一个值为count 的计数器。
await():阻塞当前线程,将当前线程加入阻塞队列。
await(long timeout, TimeUnit unit):在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行,
countDown():对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。


一:看如下例子,使用CountDownLatch 初始化线程数量,将map内的内容批量插入数据库。batchInsertMembers为单例方法。
讲解:开辟map个线程,开启多线程进行数据的批量入库。但是由于,这里batchInsertMembers是单例,导致数据不安全。

其实:单例和多线程没有直接关系。当然,你多个线程去访问单例对象其实是一个共享资源。所以,你的单例对象要保证是线程安全的。任何线程对单例对象的访问都需要线程安全,你的单例类中需要对一些共享访问进行互斥

        AtomicInteger executeNum = new AtomicInteger(0);
        Map<Integer, List<String>> map = new HashMap<>();
        map.put(1, Lists.newArrayList("a1","b1","c1"));
        map.put(2, Lists.newArrayList("a2","b2","c2"));
        
        CountDownLatch latch = new CountDownLatch(map.size());
        for (Map.Entry<Integer, List<String>> entry : map.entrySet()) {
            ThreadUtil.getDefaultExecutor().execute(() -> {
                executeNum.updateAndGet(v -> v + batchInsertMembers(entry.getValue()));
                latch.countDown();
            });;
        }

        try {
            latch.await();
        }
        catch (InterruptedException e) {
        }


public static Integer batchInsertMembers(List<String> param) {
  int i = 0;
  while (i < param.size()) {
    if ((i + 1) < param.size()) {
    System.out.println(param.subList(i, i + 1));
    }
    else {
    System.out.println(param.subList(i, param.size()));
    }
    i += 1;
    System.out.println("i------" + i);
  }
  return param.size();
}

我们来看一下运行结果:
正常情况下,打印两个i------3之后,线程就应该结束了,但是真实情况并不是这样,所以这里多个线程去访问单例对象相当于共享资源,导致了数据不安全。

[a1]
[a2]
i------1
i------1
[b1]
i------2
[b2]
i------2
[c1]
[c2]
i------3
i------3
[a2]
i------1
[b2]
i------2
[c2]
i------3



 

二:下面我们把方法稍微做一下改造:

每次都去new TestThread(entry.getValue(), latch)
每次都new一个新的线程,这样相当于解决了单例方法共享资源的问题,而是多线程每个线程都有新开辟一个新的资源,不存在数据不安全的问题。

        AtomicInteger executeNum = new AtomicInteger(0);
        Map<Integer, List<String>> map = new HashMap<>();
        map.put(1, Lists.newArrayList("a1","b1","c1"));
        map.put(2, Lists.newArrayList("a2","b2","c2"));
CountDownLatch latch = new CountDownLatch(map.size());
        for (Map.Entry<Integer, List<String>> entry : map.entrySet()) {
            ThreadUtil.getDefaultExecutor().execute(new TestThread(entry.getValue(), latch));
        }

        try {
            latch.await();
        }
        catch (InterruptedException e) {
        }

    static class TestThread extends Thread {
        List<String> testList;

        CountDownLatch latch;

        public TestThread(List<String> testList, CountDownLatch latch) {
            this.testList = testList;
            this.latch = latch;
        }

        @Override
        public void run() {
            batchInsertMembers(testList);
            latch.countDown();
        }
    }



运行结果如下:
正常
[a1]
[a2]
i------1
[b2]
i------1
i------2
[c2]
i------3
[b1]
i------2
[c1]
i------3
 

 

三:但是这并不符合我们的诉求,因为我们想要得到执行之后的数量。这里的线程执行完毕之后,并没有进行计数,我们应该怎么做?
定义一个原子类型的计数器,保证线程安全。
AtomicInteger executeNum = new AtomicInteger(0);
这里我们不使用CountDownLatch 来进行开辟线程数,用来等待所有线程执行完毕。而使用Future方法。线程使用Callable而不使用Runnable。

原理和上面ThreadUtil.getDefaultExecutor().execute(new TestThread(entry.getValue(), latch));一样,不过此方法可以用来统计线程返回值。

        AtomicInteger executeNum = new AtomicInteger(0);

        Map<Integer, List<String>> map = new HashMap<>();
        map.put(1, Lists.newArrayList("a1","b1","c1"));
        map.put(2, Lists.newArrayList("a2","b2","c2"));
        
        List<Future> futureList = new ArrayList<>();

        for (Map.Entry<Integer, List<String>> entry : map.entrySet()) {
            futureList.add(ThreadUtil.getDefaultExecutor()
                    .submit(new TestCallable(entry.getValue())));
        }

        // 汇总所有线程的查询数据
        for (Future future : futureList) {
            try {
                executeNum.addAndGet((Integer) future.get());
            }
            catch (Exception e) {
                System.out.println("error");
            }
        }

        System.out.println(executeNum.get());

        static class TestCallable implements Callable<Integer> {
        List<String> testList;

        public TestCallable(List<String> testList) {
            this.testList = testList;
        }

        @Override
        public Integer call() throws Exception {
            return batchInsertMembers(testList);
        }
    }
        

 

posted @ 2022-10-13 11:37  kelelipeng  阅读(975)  评论(0编辑  收藏  举报