java中使用多线程代替for循环(解决主线程提前结束问题)

本文介绍了Java并发工具类CountDownLatch的原理和使用方法,通过案例展示了如何利用CountDownLatch实现多线程等待与并发控制。在并发查询场景中,改造for循环为多线程查询并使用CountDownLatch确保所有查询完成后执行后续操作,从而提高效率并避免数据错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用之前先介绍一个并发需要用到的方法:
 

CountDownLatch
CountDownLatch(也叫闭锁)是一个同步协助类,允许一个或多个线程等待,直到其他线程完成操作集。

CountDownLatch 使用给定的计数值(count)初始化。await 方法会阻塞直到当前的计数值(count)由于 countDown 方法的调用达到 0,count 为 0 之后所有等待的线程都会被释放,并且随后对await方法的调用都会立即返回。
构造方法

//参数count为计数值
public CountDownLatch(int count) {};  

常用方法:

// 调用 await() 方法的线程会被挂起,它会等待直到 count 值为 0 才继续执行
public void await() throws InterruptedException {};

// 和 await() 类似,若等待 timeout 时长后,count 值还是没有变为 0,不再等待,继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {};

// 会将 count 减 1,直至为 0
public void countDown() {};

使用案例

  1. 首先是创建实例 CountDownLatch countDown = new CountDownLatch(2);
  2. 需要同步的线程执行完之后,计数 -1, countDown.countDown();
  3. 需要等待其他线程执行完毕之后,再运行的线程,调用 countDown.await()实现阻塞同步。
  4. 如下。

应用场景
        CountDownLatch 一般用作多线程倒计时计数器,强制它们等待其他一组(CountDownLatch的初始化决定)任务执行完成。

CountDownLatch的两种使用场景:

  1. 让多个线程等待,模拟并发。
  2. 让单个线程等待,多个线程(任务)完成后,进行汇总合并。

场景 1:模拟并发

import java.util.concurrent.CountDownLatch;

/**
 * 让多个线程等待:模拟并发,让并发线程一起执行
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(1);
        
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    // 等待
                    countDownLatch.await();
                    String parter = "【" + Thread.currentThread().getName() + "】";
                    System.out.println(parter + "开始执行……");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        Thread.sleep(2000);
       
        countDownLatch.countDown();
    }
}

场景 2:多个线程完成后,进行汇总合并

很多时候,我们的并发任务,存在前后依赖关系;比如数据详情页需要同时调用多个接口获取数据,并发请求获取到数据后、需要进行结果合并;或者多个数据操作完成后,需要数据 check;这其实都是:在多个线程(任务)完成后,进行汇总合并的场景。

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * 让单个线程等待:多个线程(任务)完成后,进行汇总合并
 */
public class CountDownLatchTest3 {

    //用于聚合所有的统计指标
    private static Map map = new ConcurrentHashMap();
    //创建计数器,这里需要统计4个指标
    private static CountDownLatch countDownLatch = new CountDownLatch(4);

    public static void main(String[] args) throws Exception {

        //记录开始时间
        long startTime = System.currentTimeMillis();

        Thread countUserThread = new Thread(() -> {
            try {
                System.out.println("正在统计新增用户数量");
                Thread.sleep(3000);//任务执行需要3秒
                map.put("userNumber", 100);//保存结果值
                System.out.println("统计新增用户数量完毕");
                countDownLatch.countDown();//标记已经完成一个任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread countOrderThread = new Thread(() -> {
            try {
                System.out.println("正在统计订单数量");
                Thread.sleep(3000);//任务执行需要3秒
                map.put("countOrder", 20);//保存结果值
                System.out.println("统计订单数量完毕");
                countDownLatch.countDown();//标记已经完成一个任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread countGoodsThread = new Thread(() -> {
            try {
                System.out.println("正在商品销量");
                Thread.sleep(3000);//任务执行需要3秒
                map.put("countGoods", 300);//保存结果值
                System.out.println("统计商品销量完毕");
                countDownLatch.countDown();//标记已经完成一个任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread countmoneyThread = new Thread(() -> {
            try {
                System.out.println("正在总销售额");
                Thread.sleep(3000);//任务执行需要3秒
                map.put("countMoney", 40000);//保存结果值
                System.out.println("统计销售额完毕");
                countDownLatch.countDown();//标记已经完成一个任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        //启动子线程执行任务
        countUserThread.start();
        countGoodsThread.start();
        countOrderThread.start();
        countmoneyThread.start();

        try {
            //主线程等待所有统计指标执行完毕
            countDownLatch.await();
            long endTime = System.currentTimeMillis();//记录结束时间
            System.out.println("------统计指标全部完成--------");
            System.out.println("统计结果为:" + map);
            System.out.println("任务总执行时间为" + (endTime - startTime) + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

接下来进入正题:

使用多线程代替for循环提高查询效率,并且防止主线程提前结束导致其他线程数据错误

直接上代码:

@Override
    public AppResponse getLocations() throws InterruptedException {
        List<GetLocationVO> vos = new ArrayList<>();
        vos = projectDao.getLocationOne();    
//      原来的代码
//        for (GetLocationVO vo : vos) {
//            List<LocationVO> children = projectDao.getLocationChildren(vo.getId());
//            vo.setChildren(children);
//        }
        //改造后的代码
        Thread(vos,10);
        return AppResponse.success("查询成功",vos);
    }

    //此处有加锁
    public synchronized void Thread(List<GetLocationVO> list, int nThread) throws InterruptedException {
        if (CollectionUtils.isEmpty(list) || nThread <= 0 || CollectionUtils.isEmpty(list)) {
            return;
        }
        CountDownLatch latch = new CountDownLatch(list.size());//创建一个计数器(大小为当前数组的大小,确保所有执行完主线程才结束)
        ExecutorService pool = Executors.newFixedThreadPool(nThread);//创建一个固定的线程池
        for (GetLocationVO vo : list) {
            pool.execute(() -> {
                //处理的业务
                List<LocationVO> children = projectDao.getLocationChildren(vo.getId());
                vo.setChildren(children);
                latch.countDown();
            });
        }
        latch.await();
        pool.shutdown();
    }

### Java多线程常见面试题及其解答 #### 1. **什么是进程?什么是线程?两者的区别是什么?** 进程是操作系统资源分配的基本单位,而线程是CPU调度和分派的基本单位。线程属于某个特定的进程并依赖于它存在[^3]。 #### 2. **线程有哪些状态?它们之间是如何流转的?** 线程的状态主要包括新创建 (New)、可运行 (Runnable)、阻塞 (Blocked)、等待 (Waiting) 和终止 (Terminated)。线程通过调用诸如 `start()`、`join()` 或 `sleep(long millis)` 方法在这些状态间转换[^3]。 #### 3. **如何在线程间共享数据?** 可以通过使用共享变量或者借助并发集合类如 `ConcurrentHashMap` 来实现多个线程之间的数据共享。另外,也可以采用生产者消费者模式并通过阻塞队列来完成这一目标[^2]。 #### 4. **线程中的 `wait()` 和 `sleep()` 方法有何区别?** - `wait()` 是 Object 类的方法,使当前线程进入等待状态直到其他线程调用了同一对象上的 notify() 或 notifyAll() 方法;此方法释放锁。 - `sleep()` 则是 Thread 类的静态方法,让当前线程暂停指定的时间间隔,在这段时间里不会放弃已持有的任何锁。 #### 5. **实现线程的方式有哪些?** 主要有三种方式:继承 `Thread` 类、实现 `Runnable` 接口以及使用 `Callable` 并配合 `FutureTask` 使用以支持返回结果的功能[^3]。 #### 6. **线程中的 `start()` 和 `run()` 方法的区别是什么?** 调用 `start()` 方法会启动一个新的线程,并由 JVM 调度执行其中定义的行为逻辑(即 run 方法的内容)。如果直接调用 `run()` 方法,则是在主线程中顺序执行其内部代码,而不是开启新的线程。 #### 7. **如何优雅地终止一个线程?** 一种常用的做法是设置退出标志位,比如通过 volatile 变量控制循环条件从而安全结束线程工作周期。另一种则是利用 interrupt 方法发送中断信号给目标线程使其能够响应中断请求正常退出[^3]。 #### 8. **多线程同步有哪些方法?** 可以使用 synchronized 关键字来进行方法级或块级别的锁定操作达到互斥访问的目的。对于更细粒度的控制还可以考虑 ReentrantLock 提供的各种功能选项。 #### 9. **volatile 关键字的作用是什么?与 Synchronized 的区别在哪里?** Volatile 主要用来保证内存可见性和防止指令重排序问题,但它并不具备加锁的能力因此不能替代 synchronized 实现复杂的临界区保护机制[^3]。 #### 10. **线程怎样返回结果?如何获取?** 通过实现 Callable 接口代替传统的 Runnable ,再结合 Future/FutureTask 对象就可以方便地得到异步计算后的成果信息[^4]。 --- ### 示例代码展示 以下是有关如何使用 `Callable` 返回线程执行结果的例子: ```java import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> callable = () -> { int sum = 0; for(int i=1;i<=10;i++) { sum +=i; } return sum; // 返回计算结果 }; FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); System.out.println("Sum of numbers from 1 to 10 is "+futureTask.get()); } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值