多线程处理任务并合并数据

一、线程池创建四种方式

Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定时线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

二、有返回值的多线程

ExecutorService接口继承自Executor,Executor中的execute方法无返回值,ExecutorService接口中的方法有返回值。

三、计数器使用

CountDownLatch也是juc包中的一个类,类似倒计时计数器,创建对象时通过构造方法设置初始值,调用CountDownLatch对象的await()方法则处于等待状态,调用countDown()方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。

有了计数器就可以暂时将主线程阻塞,等异步的多线程全部执行完毕并返回结果后,再继续执行主线程。

四、线程安全问题

有了上面线程池跟计数器的基础,现在可以动手写一个多线程处理任务并合并数据的demo了。

大致思路就是:创建一个定长的线程池,长度为10,计数器初始值也设置为10。每执行一次,将计数器减一,并且将执行结果添加到list集合中,最终多线程全部执行完毕后,计数器停止等待 主线程继续往下执行,返回list。

public static List<String> getExecutorService() throws InterruptedException{
		System.out.println("开始执行多线程...");
		long startTime = System.currentTimeMillis();
		List<String> list = new ArrayList<>();//存放返回结果
		CountDownLatch countDownLatch = new CountDownLatch(10);
		ExecutorService executorService = Executors.newFixedThreadPool(10);
		for (int i = 0; i < 10; i++) {
			Runnable runnable = new Runnable(){

				@Override
				public void run() {
					 try {
						Thread.sleep(3000);
                        list.add(UUID.randomUUID().toString());
                        System.out.println("当前线程name : "+Thread.currentThread().getName());
                        countDownLatch.countDown();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			};
			executorService.execute(runnable);
		}
		countDownLatch.await();
		System.out.println("submit总共cost 时间:" + (System.currentTimeMillis()-startTime)/1000 + "秒");
		executorService.shutdown();
		return list;
	}

 执行结果如下:

十个线程全部工作,但是返回值中却有null值,跟想要的结果有点出入,为啥呢?

原因在于:ArrayList是非线程安全的。ArrayList的add方法中有size++,不是一个原子操作,所以线程不安全。

 五、CopyOnWriteArrayList的用法

part4中提到的问题 解决方案很简单,将ArrayList换成CopyOnWriteArrayList即可。

	public static List<String> getExecutorService() throws InterruptedException{
		System.out.println("开始执行多线程...");
		long startTime = System.currentTimeMillis();
		List<String> list = new CopyOnWriteArrayList<>();//存放返回结果
		CountDownLatch countDownLatch = new CountDownLatch(10);
		ExecutorService executorService = Executors.newFixedThreadPool(10);
		for (int i = 0; i < 10; i++) {
			Runnable runnable = new Runnable(){

				@Override
				public void run() {
					 try {
						Thread.sleep(3000);
                        list.add(UUID.randomUUID().toString());
                        System.out.println("当前线程name : "+Thread.currentThread().getName());
                        countDownLatch.countDown();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			};
			executorService.execute(runnable);

            // 或者用CompletableFuture.runAsync()执行多线程任务
            CompletableFuture.runAsync(
                ()->{
                    try {
                        Thread.sleep(3000);
                        list.add(UUID.randomUUID().toString());
                        System.out.println("当前线程name : "+Thread.currentThread().getName());
                        countDownLatch.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }, executorService);
		}
		countDownLatch.await();
		System.out.println("submit总共cost 时间:" + (System.currentTimeMillis()-startTime)/1000 + "秒");
		executorService.shutdown();
		return list;
	}

 CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为"写时复制器"。

优点:读操作时性能很高,因为不需要任何同步措施,适用于读多写少的并发场景。

缺点:①.每次写操作都要copy原容器,频繁的GC,内存压力大。②.由于读写分离的策略,读到的数据很可能是旧数据。

  • 4
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值