模拟多线程在java web项目中的应用--Excel文件导入

以前知道线程,也只是继承Thread或者实现Runnable接口,只是基本的皮毛。后来看了Executor框架,及其内部实现ThreadPoolExecutor,大体知道怎么回事,但是怎么用在实际的项目里,还不是没有什么头绪!后来看了 

java Web项目中什么场景中会用到java多线程?

》,大体有点明白,耗时的批量任务都可考虑用多线程优化!

个人总结:执行效率特别低,耗时特别长都可以考虑多线程。如果耗时操作本身就包含多个task,可以直接提交到线程池去执行 ;如果是很多次的循环,每个任务(task)的界限不是很明确,可以像上面“场景一”中“验证1万条url路径是否存在”的解决思路,人为划分成多个任务(比如把处理200条url作为一个任务),分别提交到线程池去执行。划分任务 --> 提交任务到线程队列

结合自己的项目,觉得把数据批量导入到数据库,比较适合用多线程优化!先简单写了个多线程模拟导入过程的列子,后来就应用实践项目 中。做完这些突然觉得下面这句话很有道理,

生活中从不缺少美,而是缺少发现美的眼睛——罗丹

实际场景需求:点击批量导入按钮,选择待导入文件,然后开始导入。

导入需要耗时,为了增加友好性,导入过程中显示导入进度。

待导入完成,显示导入成功的数据条数。


如果不需要监控测试导入进度,代码如下:

package com.lyl.thread.batchImport;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 模拟数据导入,不需要关注导入结果
 */
public class BatchImportThread {
    public int dataSize = 50;
    public void doImport(){
        try {
            //模拟导入订单数据
            final List<Integer> dataList = this.getDataList();
            long startTime = System.currentTimeMillis();
            for(int i = 0; i < dataSize; i++){
                //模拟做一条数据导入到数据库花费的时间
                Thread.sleep(30);
                System.out.println(dataList.get(i));
            }
            System.out.println("普通循环=======" + (System.currentTimeMillis() - startTime));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    //模拟导入数据
    public List<Integer> getDataList(){
        //模拟导入数据
        final List<Integer> dataList = new ArrayList<Integer>();
        for(int i = 0; i < dataSize; i++){
            dataList.add(i);
        }
        return dataList;
    }
    public void doImportByThread(){
        try {
            //模拟导入订单数据
            final List<Integer> dataList = this.getDataList();
            //拆分成多个任务,每个任务包含step条数据
            int step = 10;
            int totalTasks = (dataSize % step == 0 ? dataSize/step : (dataSize/step + 1));
            System.out.println(totalTasks);
            final CountDownLatch countDownLatch = new CountDownLatch(totalTasks);
            long startTime1 = System.currentTimeMillis();
            //线程数量对程序耗时有不同影响
//            ExecutorService executorService = Executors.newFixedThreadPool(15);
            ExecutorService executorService = Executors.newCachedThreadPool();
            for(int j = 0; j < dataSize; j=j+step){
                final int start = j;
                final int perCount = (dataSize - start) < step ? (dataSize - start) : step;
                System.out.println(perCount);
                executorService.execute(new Runnable() {
                    public void run() {
                        try {
                            System.out.println(" start == " + start + " , count" + perCount);
                            for(int n = 0; n < perCount; n++){
                                System.out.println(dataList.get(start + n));
                                //模拟做一条数据导入到数据库花费的时间
                                Thread.sleep(30);
                            }
                            countDownLatch.countDown();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            countDownLatch.await();
            System.out.println("线程池循环耗时=======" + (System.currentTimeMillis() - startTime1));
            executorService.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        BatchImportThread batchImportThread = new BatchImportThread();
        batchImportThread.doImport();
        batchImportThread.doImportByThread();
    }

}  


多线程模拟实际场景需求的代码:

package com.lyl.thread.batchImport;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 模拟订单导入并显示进度,spring 中此类是一个单例,多个用户同时导入订单,会同时访问此类。
 *  实际web项目中,把成功导入的订单数(tradeIdsuccessCntMap.get(tradeId))放到redis *  jsp页面在发出导入请求的时候,开启一个setTime函数根据tradeId每隔一秒向服务器请求导入成功的订单数,
 *  doImport方法结束,导入请求结束,jsp页面显示服务器返回的导入成功的订单数,并取消setTime函数
 */
public class BatchImportThreadProgress {
    //是否结束
    private volatile boolean isStop = false;
    //多个导入请求会同时访问此类,防止多个导入互相更改导入成功订单数量,所以设置为Map类型,
    // 使一个tradeId对应一个导入成功订单数量,tradeId对应successCntMap中的key,表示本次请求导入成功的数量
    private volatile Map<String, Integer> successCntMap = new HashMap<String, Integer>();

    /**
     * 模拟导入
     * @param tradeId
     */
    public void doImport(final String tradeId){
        try {
            final List<Integer> dataList = new ArrayList<Integer>();
            //模拟导入订单
            int dataSize = 9;
            for(int i = 0; i < dataSize; i++){
                dataList.add(i);
            }
            //初始化
            successCntMap.put(tradeId, 0);
            //多线程导入
            ExecutorService executorService = Executors.newFixedThreadPool(9);
            int step = 2;
            int cysSum = (dataSize % step == 0 ? dataSize/step : (dataSize/step + 1));
//            System.out.println("循环次数===" + cysSum);
            final CountDownLatch countDownLatch = new CountDownLatch(cysSum);
            long startTime1 = System.currentTimeMillis();
            for(int j = 0; j < dataSize; j=j+step){
                final int start = j;
                final int perCount = (dataSize - start) < step ? (dataSize - start) : step;
    //            logger.info("n==" + n);
                executorService.execute(new Runnable() {
                    public void run() {
                        try {
//                            System.out.println(new StringBuilder(tradeId).append(" === start == ").append(start).append(" , count").append(perCount).toString());
                            for(int n = 0; n < perCount; n++){
                                System.out.println(new StringBuilder(tradeId).append(" === 导入数据 == ").append(dataList.get(start + n)).toString());
                                Thread.sleep(300);
                                synchronized (tradeId){
                                    successCntMap.put(tradeId, successCntMap.get(tradeId) + 1);
                                }
                                //把导入成功的订单数记录到Redis()
                                //redis.put(tradeId, successCntMap.get(tradeId));
                            }
                            countDownLatch.countDown();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            //等所有线程都结束,才返回结果
            countDownLatch.await();
            System.out.println(new StringBuilder(tradeId).append(" == 线程池循环耗时 === ").append((System.currentTimeMillis() - startTime1))
                    .append(" , 成功导入数据条数:").append(successCntMap.get(tradeId)).toString());
            executorService.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final BatchImportThreadProgress batchImportThreadProgress = new BatchImportThreadProgress();
        //模拟多个(count)用户,同时导入订单
        int count = 2;
        final CountDownLatch countDownLatch = new CountDownLatch(count);
        for(int i = 0; i < count; i++){
            final String tradeId = "tradeId"+i;
            new Thread(new Runnable() {
                public void run() {
                    batchImportThreadProgress.doImport(tradeId);
                    countDownLatch.countDown();
                }
            }).start();

        }
        //获得导入进度 - -web项目中不需要这样监控,需要单独写一个接口,jsp页面根据tradeId每隔一秒(settime函数)请求一次该接口,
        // 接口从redis里缓存的进度返回给jsp,并刷新结果
        new Thread(new Runnable() {
            public void run() {
                System.out.println("-------获得导入进度--------");
                while (!batchImportThreadProgress.isStop){
                    try {
                        String tradeId = "";
                        for(int i = 0; i < count; i++){
                            tradeId = "tradeId"+i;
                            System.out.println("---batchImportThreadProgress---" + tradeId + "---获得导入进度--------" + batchImportThreadProgress.successCntMap.get(tradeId));
                        }
                        Thread.sleep(500);//每隔0.5秒显示一次进度
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        //等待线程池结束
        countDownLatch.await();
        batchImportThreadProgress.isStop = true;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值