场景一:任务处理耗时较长,且任务不可拆分。
业务背景:任务处理耗时较长,页面迟迟等不到数据返回而导致响应超时。
解决方案:页面增加进度条展示,待任务完成再触发下一步动作。
实现方案:后台另起一个线程处理耗时任务,主线程直接返回处理业务的任务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版权协议,转载请附上原文出处链接和本声明。