线程的使用场景及实现方案

场景一:任务处理耗时较长,且任务不可拆分。

业务背景:任务处理耗时较长,页面迟迟等不到数据返回而导致响应超时。
解决方案:页面增加进度条展示,待任务完成再触发下一步动作。
实现方案:后台另起一个线程处理耗时任务,主线程直接返回处理业务的任务ID,处理任务的线程在处理的不同阶段刷新完成进度值,后台另起一接口通过任务ID查询进度返回给页面。代码模拟如下:

package thread;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class TestThreadApplication1 {

    // 模拟存放进度的缓冲
    private static Map<String, Integer> map = new HashMap<>();

    public static void main(String[] args) {
        // 前台请求后台执行耗时任务立即获取一个任务编号
        String taskNo = doTask();

        // 前台再根据任务编号间隔一秒查询进度并刷新页面进度条
        int progress = queryProgress(taskNo);
    }

    // 另起线程执行耗时任务,主线程不等待直接返回任务编号给前台页面。
    public static String doTask() {
        // 1、给耗时任务编一个任务编号供前台查询进度
        String taskNo = UUID.randomUUID().toString();

        // 2、模拟耗时任务
        Runnable task = new Runnable() {
            @Override
            public void run() {
                // 这里处理长耗时任务
                try {
                    // 模拟执行一段程序耗时
                    Thread.sleep(2000);
                    int progress = map.getOrDefault(taskNo, 0);
                    map.put(taskNo, progress + 20);

                    // 模拟又执行一段程序耗时
                    Thread.sleep(2000);
                    progress = map.get(taskNo);
                    map.put(taskNo, progress + 30);

                    // 模拟又执行一段程序耗时
                    Thread.sleep(2000);
                    progress = map.get(taskNo);
                    map.put(taskNo, progress + 40);

                    // 模拟任务执行完了
                    Thread.sleep(1000);
                    map.put(taskNo, 100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };

        // 把任务交给线程并启动线程
        new Thread(task).start();

        // 主线程继续执行直接返回任务编号给前台
        return taskNo;
    }

    // 页面根据任务编号调用此接口查询进度
    public static int queryProgress(String taskNo) {
        // 从缓存中取出任务的进度返回给页面
        return map.get(taskNo);
    }
}

场景二:任务处理耗时较长,但任务可拆分。

这种情况可把任务拆分几个子任务,交给几个线程并行处理,提高并发性,减少任务的处理时间。一个任务主线程自己处理要耗时4秒,如果把该任务拆分成四个小任务分别交给四个线程去处理,时间上大概1秒左右即可完成。
比如把四个excel数据文件的数据解析后保存到数据库表中,就可以用四个线程,每个线程解析一个数据文件。

import org.apache.commons.lang3.RandomUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class TestThreadApplication2 {
    public static void main(String[] args) {
        // 模拟存在四个文件
        List<String> filePathList = Arrays.asList("D:/file1.xlsx", "D:/file2.xlsx", "D:/file3.xlsx", "D:/file4.xlsx");

        // 1、主线程自己解析,要一个解析完才能解析下一个,使用循环,时间是四次解析的时间相加
        List<Map<String, String>> data = new ArrayList<>();
        long time1 = System.currentTimeMillis();
        filePathList.forEach(filePath -> data.addAll(parseFile(filePath)));
        System.out.println("主线程自个解析共耗时:" + (System.currentTimeMillis() - time1));

        // 2、如果开四个线程进行解析,四个线程并发执行去分别解析一个文件,解析时间是四个线程最长的那个所花费的时间。
        long time2 = System.currentTimeMillis();
        for (int i = 0; i < 4; i++) {
            int j = i;
            new Thread(() -> {
                data.addAll(parseFile(filePathList.get(j)));
                System.out.println(Thread.currentThread().getName() + "解析文件耗时:" + (System.currentTimeMillis() - time2));
            }, "thread" + i).start();
        }
    }

    private static List<Map<String, String>> parseFile(String filePath) {
        List<Map<String, String>> data = new ArrayList<>();
        try {
            // 模拟解析一个文件,耗时在1~1.6秒之间
            Thread.sleep(RandomUtils.nextLong(1000,1600));
            // 获取到文件数据放到data中
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }
}

模拟程序运行结果:
Connected to the target VM, address: '127.0.0.1:49879', transport: 'socket'
主线程自个解析共耗时:5251
thread2解析文件耗时:1209
thread0解析文件耗时:1241
thread1解析文件耗时:1446
thread3解析文件耗时:1449
Disconnected from the target VM, address: '127.0.0.1:49879', transport: 'socket'

Process finished with exit code 0

场景三:某一任务要等其他任务都处理完后才可以开始。

比如赛跑,裁判汇总比赛成绩,要等参赛运动员都跑完才可以。

可使用用CountDownLatch或CyclicBarrier实现,
package thread;

import org.apache.commons.lang3.RandomUtils;

import java.util.concurrent.CountDownLatch;

/**
 * 模拟场景:百米赛跑,
 * 四名运动员等待裁判发号开跑命令后开始跑,
 * 裁判等待四名运动员都跑完比赛开始汇总成绩
 */
public class TestThreadApplication3 {
    public static void main(String[] args) {
        // 裁判号令记录
        CountDownLatch caiPanLatch = new CountDownLatch(1);
        // 记录运动员跑完比赛
        CountDownLatch runnerLatch = new CountDownLatch(4);

        for (int i = 0; i < 4; i++) {
            new Thread(() -> {
                try {
                    caiPanLatch.await();
                    System.out.println(Thread.currentThread().getName() + "看到裁判发令枪冒烟后开跑...");
                    // 模拟运动员跑完百米耗时在9.5~13秒之间
                    Thread.sleep(RandomUtils.nextLong(9500, 13000));
                    System.out.println(Thread.currentThread().getName() + "跑完了比赛");
                    runnerLatch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "00" + i + "号运动员").start();
        }

        System.out.println("裁判发号令开跑!");
        caiPanLatch.countDown();

        try {
            runnerLatch.await();
            System.out.println("所有运动员都跑完了,裁判开始汇总成绩...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

模拟代码运行结果如下:
Connected to the target VM, address: '127.0.0.1:50772', transport: 'socket'
裁判发号令开跑!
000号运动员看到裁判发令枪冒烟后开跑...
002号运动员看到裁判发令枪冒烟后开跑...
003号运动员看到裁判发令枪冒烟后开跑...
001号运动员看到裁判发令枪冒烟后开跑...
001号运动员跑完了比赛
000号运动员跑完了比赛
002号运动员跑完了比赛
003号运动员跑完了比赛
所有运动员都跑完了,裁判开始汇总成绩...
Disconnected from the target VM, address: '127.0.0.1:50772', transport: 'socket'

场景四:某几个任务要等同一个命令后同时开始处理。

比如赛跑,运动员开始跑要等裁判发号令,没有收到命令不允许开始。

参考场景三,裁判要用一个CountDownLatch

场景五:多项目积分赛,每轮比赛都要在上一轮比赛所有选手比完后,方可以开始下一轮比赛。

     * 赛跑达人选拔赛
     * 8名运动员赛跑争取赛跑达人。
     * 共4轮比赛,第一轮100m,第二轮400m,第三轮800m,第四轮1600m
     * 每轮比赛第一名得8分,第二名得7分,第三名3分,依次类推第八名1分。
     * 这样四轮下来得分最多者为冠军,得分第二多为亚军,第三朵的为季军.
package thread;

import org.apache.commons.lang3.RandomUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 循环栅栏(CyclicBarrier)和计数器(CountDownLatch)一样
 * 都可以等到一个障碍后开始执行,但是循环栅栏可以重复使用,计数器则不可以
 */
public class TestThreadApplication4 {
    /**
     * 赛跑达人选拔赛
     * 8名运动员赛跑争取赛跑达人。
     * 共4轮比赛,第一轮100m,第二轮400m,第三轮800m,第四轮1600m
     * 每轮比赛第一名得8分,第二名得7分,第三名3分,依次类推第八名1分。
     * 这样四轮下来得分最多者为冠军,得分第二多为亚军,第三朵的为季军
     *
     * @param args
     */
    public static void main(String[] args) {

        // 运动员每轮比赛数据
        Map<Integer, Map<String, Integer>> scoreMap = new HashMap<>();
        // 运动员累计得分数据
        Map<String, Integer> totalScoreMap = new HashMap<>();
        CountDownLatch caiPan = new CountDownLatch(1);

        AtomicInteger num = new AtomicInteger(0);
        AtomicInteger count = new AtomicInteger(0);
        CyclicBarrier brrier = new CyclicBarrier(8, new Runnable() {
            @Override
            public void run() {
                int n = num.get();
                switch (n) {
                    case 1:
                        System.out.println("第1轮100米比赛最后一名:" + Thread.currentThread().getName() + "也跑完了");
                        scoreMap.get(1).entrySet().stream()
                                  .sorted((entry1, entry2) -> entry1.getValue().compareTo(entry2.getValue()));
                        System.out.println("第1轮100米比赛得分情况:" + scoreMap.get(1).toString());
                        break;
                    case 2:
                        System.out.println("第2轮400米比赛最后一名:" + Thread.currentThread().getName() + "也跑完了");
                        System.out.println("第2轮400米比赛得分情况:" + scoreMap.get(2).toString());
                        System.out.println("两轮累计得分情况:" + totalScoreMap.toString());
                        break;
                    case 3:
                        System.out.println("第3轮800米比赛最后一名:" + Thread.currentThread().getName() + "也跑完了");
                        System.out.println("第3轮800米比赛得分情况" + scoreMap.get(3).toString());
                        System.out.println("三轮累计得分情况:" + totalScoreMap.toString());
                        break;
                    case 4:
                        System.out.println("第4轮1600米比赛最后一名:" + Thread.currentThread().getName() + "也跑完了");
                        System.out.println("第4轮1600米比赛得分情况" + scoreMap.get(4).toString());
                        System.out.println("四轮累计得分情况:" + totalScoreMap.toString());
                        break;
                }
            }
        });

        for (int i = 1; i <= 8; i++) {
            Thread thread = new Thread(() -> {
                try {
                    // 第一轮
                    caiPan.await();
                    num.set(1);
                    System.out.println("第1轮100米比赛开始:" + Thread.currentThread().getName() + "开始跑");
                    Thread.sleep(RandomUtils.nextInt(1, 9));
                    synchronized (TestCyclicBarrier.class) {
                        System.out.println("第1轮100米比赛:" + Thread.currentThread().getName() + "跑完了");
                        // 名次
                        count.set(count.get() + 1);
                        // 本轮得分
                        int score = 9 - count.get();
                        String runnerName = Thread.currentThread().getName();
                        Map<String, Integer> runerScore = scoreMap.getOrDefault(1, new HashMap<>());
                        runerScore.put(runnerName, score);
                        scoreMap.put(1, runerScore);
                        // 累计得分
                        int oldValue = totalScoreMap.getOrDefault(runnerName, 0);
                        totalScoreMap.put(runnerName, oldValue + score);
                    }
                    brrier.await();

                    // 第二轮
                    num.set(2);
                    count.set(0);
                    brrier.reset();
                    System.out.println("第2轮400米比赛开始:" + Thread.currentThread().getName() + "开始跑");
                    Thread.sleep(RandomUtils.nextInt(1, 9));
                    synchronized (TestCyclicBarrier.class) {
                        System.out.println("第2轮400米比赛:" + Thread.currentThread().getName() + "跑完了");
                        // 名次
                        count.set(count.get() + 1);
                        // 本轮得分
                        int score = 9 - count.get();
                        String runnerName = Thread.currentThread().getName();
                        Map<String, Integer> runerScore = scoreMap.getOrDefault(2, new HashMap<>());
                        runerScore.put(runnerName, score);
                        scoreMap.put(2, runerScore);
                        // 累计得分
                        int oldValue = totalScoreMap.getOrDefault(runnerName, 0);
                        totalScoreMap.put(runnerName, oldValue + score);
                    }
                    brrier.await();

                    // 第三轮
                    num.set(3);
                    count.set(0);
                    brrier.reset();
                    System.out.println("第3轮800米比赛开始:" + Thread.currentThread().getName() + "开始跑");
                    Thread.sleep(RandomUtils.nextInt(1, 9));
                    synchronized (TestCyclicBarrier.class) {
                        System.out.println("第3轮800米比赛:" + Thread.currentThread().getName() + "跑完了");
                        // 名次
                        count.set(count.get() + 1);
                        // 本轮得分
                        int score = 9 - count.get();
                        String runnerName = Thread.currentThread().getName();
                        Map<String, Integer> runerScore = scoreMap.getOrDefault(3, new HashMap<>());
                        runerScore.put(runnerName, score);
                        scoreMap.put(3, runerScore);
                        // 累计得分
                        int oldValue = totalScoreMap.getOrDefault(runnerName, 0);
                        totalScoreMap.put(runnerName, oldValue + score);
                    }
                    brrier.await();

                    // 第四轮
                    num.set(4);
                    count.set(0);
                    brrier.reset();
                    System.out.println("第4轮1600米比赛开始:" + Thread.currentThread().getName() + "开始跑");
                    Thread.sleep(RandomUtils.nextInt(1, 9));
                    synchronized (TestCyclicBarrier.class) {
                        System.out.println("第4轮1600米比赛:" + Thread.currentThread().getName() + "跑完了");
                        // 名次
                        count.set(count.get() + 1);
                        // 本轮得分
                        int score = 9 - count.get();
                        String runnerName = Thread.currentThread().getName();
                        Map<String, Integer> runerScore = scoreMap.getOrDefault(4, new HashMap<>());
                        runerScore.put(runnerName, score);
                        scoreMap.put(4, runerScore);
                        // 累计得分
                        int oldValue = totalScoreMap.getOrDefault(runnerName, 0);
                        totalScoreMap.put(runnerName, oldValue + score);
                    }
                    brrier.await();

                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
            thread.setName(i + "号运动员");
            thread.start();
        }

        System.out.println("比赛开始");
        caiPan.countDown();
    }
}

模拟代码运行结果如下:
Connected to the target VM, address: '127.0.0.1:51226', transport: 'socket'
比赛开始
第1轮100米比赛开始:5号运动员开始跑
第1轮100米比赛开始:6号运动员开始跑
第1轮100米比赛开始:4号运动员开始跑
第1轮100米比赛开始:7号运动员开始跑
第1轮100米比赛开始:2号运动员开始跑
第1轮100米比赛开始:3号运动员开始跑
第1轮100米比赛开始:1号运动员开始跑
第1轮100米比赛开始:8号运动员开始跑
第1轮100米比赛:6号运动员跑完了
第1轮100米比赛:4号运动员跑完了
第1轮100米比赛:8号运动员跑完了
第1轮100米比赛:7号运动员跑完了
第1轮100米比赛:5号运动员跑完了
第1轮100米比赛:3号运动员跑完了
第1轮100米比赛:1号运动员跑完了
第1轮100米比赛:2号运动员跑完了
第1轮100米比赛最后一名:2号运动员也跑完了
第1轮100米比赛得分情况:{5号运动员=4, 4号运动员=7, 3号运动员=3, 6号运动员=8, 8号运动员=6, 7号运动员=5, 1号运动员=2, 2号运动员=1}
第2轮400米比赛开始:2号运动员开始跑
第2轮400米比赛开始:6号运动员开始跑
第2轮400米比赛开始:7号运动员开始跑
第2轮400米比赛开始:8号运动员开始跑
第2轮400米比赛开始:4号运动员开始跑
第2轮400米比赛开始:1号运动员开始跑
第2轮400米比赛开始:3号运动员开始跑
第2轮400米比赛开始:5号运动员开始跑
第2轮400米比赛:2号运动员跑完了
第2轮400米比赛:5号运动员跑完了
第2轮400米比赛:7号运动员跑完了
第2轮400米比赛:1号运动员跑完了
第2轮400米比赛:4号运动员跑完了
第2轮400米比赛:8号运动员跑完了
第2轮400米比赛:6号运动员跑完了
第2轮400米比赛:3号运动员跑完了
第2轮400米比赛最后一名:3号运动员也跑完了
第2轮400米比赛得分情况:{5号运动员=7, 4号运动员=4, 3号运动员=1, 6号运动员=2, 8号运动员=3, 7号运动员=6, 1号运动员=5, 2号运动员=8}
两轮累计得分情况:{5号运动员=11, 4号运动员=11, 3号运动员=4, 6号运动员=10, 8号运动员=9, 7号运动员=11, 1号运动员=7, 2号运动员=9}
第3轮800米比赛开始:3号运动员开始跑
第3轮800米比赛开始:2号运动员开始跑
第3轮800米比赛开始:1号运动员开始跑
第3轮800米比赛开始:6号运动员开始跑
第3轮800米比赛开始:7号运动员开始跑
第3轮800米比赛开始:5号运动员开始跑
第3轮800米比赛开始:8号运动员开始跑
第3轮800米比赛开始:4号运动员开始跑
第3轮800米比赛:7号运动员跑完了
第3轮800米比赛:2号运动员跑完了
第3轮800米比赛:3号运动员跑完了
第3轮800米比赛:1号运动员跑完了
第3轮800米比赛:4号运动员跑完了
第3轮800米比赛:5号运动员跑完了
第3轮800米比赛:6号运动员跑完了
第3轮800米比赛:8号运动员跑完了
第3轮800米比赛最后一名:8号运动员也跑完了
第3轮800米比赛得分情况{5号运动员=3, 3号运动员=6, 4号运动员=4, 6号运动员=2, 8号运动员=1, 7号运动员=8, 1号运动员=5, 2号运动员=7}
三轮累计得分情况:{5号运动员=14, 4号运动员=15, 3号运动员=10, 6号运动员=12, 8号运动员=10, 7号运动员=19, 1号运动员=12, 2号运动员=16}
第4轮1600米比赛开始:8号运动员开始跑
第4轮1600米比赛开始:7号运动员开始跑
第4轮1600米比赛开始:3号运动员开始跑
第4轮1600米比赛开始:4号运动员开始跑
第4轮1600米比赛开始:2号运动员开始跑
第4轮1600米比赛开始:6号运动员开始跑
第4轮1600米比赛开始:5号运动员开始跑
第4轮1600米比赛开始:1号运动员开始跑
第4轮1600米比赛:1号运动员跑完了
第4轮1600米比赛:5号运动员跑完了
第4轮1600米比赛:3号运动员跑完了
第4轮1600米比赛:4号运动员跑完了
第4轮1600米比赛:8号运动员跑完了
第4轮1600米比赛:2号运动员跑完了
第4轮1600米比赛:7号运动员跑完了
第4轮1600米比赛:6号运动员跑完了
第4轮1600米比赛最后一名:6号运动员也跑完了
第4轮1600米比赛得分情况{5号运动员=7, 3号运动员=6, 4号运动员=5, 6号运动员=1, 8号运动员=4, 7号运动员=2, 1号运动员=8, 2号运动员=3}
四轮累计得分情况:{5号运动员=21, 4号运动员=20, 3号运动员=16, 6号运动员=13, 8号运动员=14, 7号运动员=21, 1号运动员=20, 2号运动员=19}
Disconnected from the target VM, address: '127.0.0.1:51226', transport: 'socket'


版权声明:本文为weixin_45421271原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。