java异步线程数_spring异步service中处理线程数限制详解
情况简介
spring项目,controller异步调用service的方法,产生大量并发。
具体业务:
前台同时传入大量待翻译的单词,后台业务接收单词,并调用百度翻译接口翻译接收单词并将翻译结果保存到数据库,前台不需要实时返回翻译结果。
处理方式:
controller接收文本调用service中的异步方法,将单词先保存到队列中,再启动2个新线程,从缓存队列中取单词,并调用百度翻译接口获取翻译结果并将翻译结果保存到数据库。
本文主要知识点:
多线程同时(异步)调用方法后,开启新线程,并限制线程数量。
代码如下:@Service
public class LgtsAsyncServiceImpl {
/** logger日志. */
public static final Logger LOGGER = Logger.getLogger(LgtsAsyncServiceImpl2.class);
private final BlockingQueue que = new LinkedBlockingQueue<>();// 待翻译的队列
private final AtomicInteger threadCnt = new AtomicInteger(0);// 当前翻译中的线程数
private final Vector existsKey = new Vector<>();// 保存已入队列的数据
private final int maxThreadCnt = 2;// 允许同时执行的翻译线程数
private static final int NUM_OF_EVERY_TIME = 50;// 每次提交的翻译条数
private static final String translationFrom = "zh";
@Async
public void saveAsync(Lgts t) {
if (Objects.isNull(t) || StringUtils.isAnyBlank(t.getGco(), t.getCode())) {
return;
}
offer(t);
save();
return;
}
private boolean offer(Lgts t) {
String key = t.getGco() + "-" + t.getCode();
if (!existsKey.contains(key)) {
existsKey.add(key);
boolean result = que.offer(t);
// LOGGER.trace("待翻译文字[" + t.getGco() + ":" + t.getCode() + "]加入队列结果[" + result
// + "],队列中数据总个数:" + que.size());
return result;
}
return false;
}
@Autowired
private LgtsService lgtsService;
private void save() {
int cnt = threadCnt.incrementAndGet();// 当前线程数+1
if (cnt > maxThreadCnt) {
// 已启动的线程大于设置的最大线程数直接丢弃
threadCnt.decrementAndGet();// +1的线程数再-回去
return;
}
GwallUser user = UserUtils.getUser();
Thread thr = new Thread() {
public void run() {
long sleepTime = 30000l;
UserUtils.setUser(user);
boolean continueFlag = true;
int maxContinueCnt = 5;// 最大连续休眠次数,连续休眠次数超过最大休眠次数后,while循环退出,当前线程销毁
int continueCnt = 0;// 连续休眠次数
while (continueFlag) {// 队列不为空时执行
if (Objects.isNull(que.peek())) {
try {
if (continueCnt > maxContinueCnt) {
// 连续休眠次数达到最大连续休眠次数,当前线程将销毁。
continueFlag = false;
continue;
}
// 队列为空,准备休眠
Thread.sleep(sleepTime);
continueCnt++;
continue;
} catch (InterruptedException e) {
// 休眠失败,无需处理
e.printStackTrace();
}
}
continueCnt = 0;// 重置连续休眠次数为0
List params = new ArrayList<>();
int totalCnt = que.size();
que.drainTo(params, NUM_OF_EVERY_TIME);
StringBuilder utf8q = new StringBuilder();
String code = "";
List needRemove = new ArrayList<>();
for (Lgts lgts : params) {
if (StringUtils.isAnyBlank(code)) {
code = lgts.getCode();
}
// 移除existsKey中保存的key,以免下面翻译失败时再次加入队列时,加入不进去
String key = lgts.getGco() + "-" + lgts.getCode();
existsKey.remove(key);
if (!code.equalsIgnoreCase(lgts.getCode())) {// 要翻译的目标语言与当前列表中的第一个不一致
offer(lgts);// 重新将待翻译的语言放回队列
needRemove.add(lgts);
continue;
}
utf8q.append(lgts.getGco()).append("\n");
}
params.removeAll(needRemove);
LOGGER.debug("队列中共" + totalCnt + " 个,获取" + params.size() + " 个符合条件的待翻译内容,编码:" + code);
String to = "en";
if (StringUtils.isAnyBlank(utf8q, to)) {
LOGGER.warn("调用翻译出错,未找到[" + code + "]对应的百度编码。");
continue;
}
Map result = getBaiduTranslation(utf8q.toString(), translationFrom, to);
if (Objects.isNull(result) || result.isEmpty()) {// 把没有获取到翻译结果的重新放回队列
for (Lgts lgts : params) {
offer(lgts);
}
LOGGER.debug("本次翻译结果为空。");
continue;
}
int sucessCnt = 0, ignoreCnt = 0;
for (Lgts lgts : params) {
lgts.setBdcode(to);
String gna = result.get(lgts.getGco());
if (StringUtils.isAnyBlank(gna)) {
offer(lgts);// 重新将待翻译的语言放回队列
continue;
}
lgts.setStat(1);
lgts.setGna(gna);
int saveResult = lgtsService.saveIgnore(lgts);
if (0 == saveResult) {
ignoreCnt++;
} else {
sucessCnt++;
}
}
LOGGER.debug("待翻译个数:" + params.size() + ",翻译成功个数:" + sucessCnt + ",已存在并忽略个数:" + ignoreCnt);
}
threadCnt.decrementAndGet();// 运行中的线程数-1
distory();// 清理数据,必须放在方法最后,否则distory中的判断需要修改
}
/**
* 如果是最后一个线程,清空队列和existsKey中的数据
*/
private void distory() {
if (0 == threadCnt.get()) {
// 最后一个线程退出时,执行清理操作
existsKey.clear();
que.clear();
}
}
};
thr.setDaemon(true);// 守护线程,如果主线程执行完毕,则此线程会自动销毁
thr.setName("baidufanyi-" + RandomUtils.nextInt(1000, 9999));
thr.start();// 启动插入线程
}
/**
* 百度翻译
*
* @param utf8q
* 待翻译的字符串,需要utf8格式的
* @param from
* 百度翻译语言列表中的代码
* 参见:http://api.fanyi.baidu.com/api/trans/product/apidoc#languageList
* @param to
* 百度翻译语言列表中的代码
* 参见:http://api.fanyi.baidu.com/api/trans/product/apidoc#languageList
* @return 翻译结果
*/
private Map getBaiduTranslation(String utf8q, String from, String to) {
Map result = new HashMap<>();
String baiduurlStr = "http://api.fanyi.baidu.com/api/trans/vip/translate";
if (StringUtils.isAnyBlank(baiduurlStr)) {
LOGGER.warn("百度翻译API接口URL相关参数为空!");
return result;
}
Map params = buildParams(utf8q, from, to);
if (params.isEmpty()) {
return result;
}
String sendUrl = getUrlWithQueryString(baiduurlStr, params);
try {
HttpClient httpClient = new HttpClient();
httpClient.setMethod("GET");
String remoteResult = httpClient.pub(sendUrl, "");
result = convertRemote(remoteResult);
} catch (Exception e) {
LOGGER.info("百度翻译API返回结果异常!", e);
}
return result;
}
private Map convertRemote(String remoteResult) {
Map result = new HashMap<>();
if (StringUtils.isBlank(remoteResult)) {
return result;
}
JSONObject jsonObject = JSONObject.parseObject(remoteResult);
JSONArray trans_result = jsonObject.getJSONArray("trans_result");
if (Objects.isNull(trans_result) || trans_result.isEmpty()) {
return result;
}
for (Object object : trans_result) {
JSONObject trans = (JSONObject) object;
result.put(trans.getString("src"), trans.getString("dst"));
}
return result;
}
private Map buildParams(String utf8q, String from, String to) {
if (StringUtils.isBlank(from)) {
from = "auto";
}
Map params = new HashMap();
String skStr = "sk";
String appidStr = "appid";
if (StringUtils.isAnyBlank(skStr, appidStr)) {
LOGGER.warn("百度翻译API接口相关参数为空!");
return params;
}
params.put("q", utf8q);
params.put("from", from);
params.put("to", to);
params.put("appid", appidStr);
// 随机数
String salt = String.valueOf(System.currentTimeMillis());
params.put("salt", salt);
// 签名
String src = appidStr + utf8q + salt + skStr; // 加密前的原文
params.put("sign", MD5Util.md5Encrypt(src).toLowerCase());
return params;
}
public static String getUrlWithQueryString(String url, Map params) {
if (params == null) {
return url;
}
StringBuilder builder = new StringBuilder(url);
if (url.contains("?")) {
builder.append("&");
} else {
builder.append("?");
}
int i = 0;
for (String key : params.keySet()) {
String value = params.get(key);
if (value == null) { // 过滤空的key
continue;
}
if (i != 0) {
builder.append('&');
}
builder.append(key);
builder.append('=');
builder.append(encode(value));
i++;
}
return builder.toString();
}
/**
* 对输入的字符串进行URL编码, 即转换为%20这种形式
*
* @param input
* 原文
* @return URL编码. 如果编码失败, 则返回原文
*/
public static String encode(String input) {
if (input == null) {
return "";
}
try {
return URLEncoder.encode(input, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return input;
}
}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对谷谷点程序的支持
java异步线程数_spring异步service中处理线程数限制详解相关推荐
- 【异步编程学习笔记】JDK中的FutureTask和CompletableFuture详解(使用示例、源码)
文章目录 FutureTask概述 使用实例 类图结构 FutureTask的run()方法 FutureTask的局限性 CompletableFuture概述 CompletableFuture代 ...
- 从java多态到策略模式_设计模式中的多态——策略模式详解
2. 策略模式详解 2.1 策略模式定义 策略模式定义了一系列算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户端而独立的变化. 可以使用多态进行类比来理解策略模 ...
- java的循环控制结构有哪些_java中的控制结构(if,循环)详解
1 说明JAVA语言中三种控制循环结构的代码形式(其他 1. while(condition){ statements; } 其中,condition是任何布尔表达式,其返回值为true 或 fals ...
- java实现图表步骤_Java 在PPT中添加混合图表过程详解
本文将介绍通过Java程序在PPT幻灯片中添加混合图表的方法,即,将不同类型的图表类型放置在同一图表中,用于展示同一时期或阶段的数据在不同参数标准下的变化情况,便于对数据的综合分析. 使用工具:Fre ...
- java mod 函数的使用方法_Java中 % 与Math.floorMod() 区别详解
%为取余(rem),Math.floorMod()为取模(mod) 取余取模有什么区别呢? 对于整型数a,b来说,取模运算或者取余运算的方法都是: 1.求 整数商: c = a/b; 2.计算模或者余 ...
- java asynchronize_Java 中synchronize函数的实例详解
Java 中synchronize函数的实例详解 java中的一个类的成员函数若用synchronized来修饰,则对应同一个对象,多个线程像调用这个对象的这个同步函数时必须等到上一个线程调用完才能由 ...
- java中math的方法_Java中Math类常用方法代码详解
近期用到四舍五入想到以前整理了一点,就顺便重新整理好经常见到的一些四舍五入,后续遇到常用也会直接在这篇文章更新... public class Demo{ public static void mai ...
- JDK中的Timer和TimerTask详解 目录结构: Timer和TimerTask 一个Timer调度的例子 如何终止Timer线程 关于cancle方式终止线程 反复执行一个任务 sche
JDK中的Timer和TimerTask详解 目录结构: Timer和TimerTask 一个Timer调度的例子 如何终止Timer线程 关于cancle方式终止线程 反复执行一个任务 schedu ...
- java mod %区别_Java中 % 与Math.floorMod() 区别详解
%为取余(rem),Math.floorMod()为取模(mod) 取余取模有什么区别呢? 对于整型数a,b来说,取模运算或者取余运算的方法都是: 1.求 整数商: c = a/b; 2.计算模或者余 ...
- java 静态 编译_Java中的动态和静态编译实例详解
Java中的动态和静态编译实例详解 首先,我们来说说动态和静态编译的问题. Q: java和javascript有什么区别? 总结了一下:有以下几点吧: 1.首先从运行环境来说java代码是在JVM上 ...
最新文章
- jmeter--入参为json类型
- 【数据挖掘】基于密度的聚类方法 - DBSCAN 方法 ( K-Means 方法缺陷 | 基于密度聚类原理及概念 | ε-邻域 | 核心对象 | 直接密度可达 | 密度可达 | 密度连接 )
- 怎么用python自制计算公式_自制计算经纬度位移 python 程序
- nginx大量TIME_WAIT的解决办法--转
- 资深美术师:3A转手游研发需要注意的方法
- 字符串池化,减少了三分之一的内存占用
- Java 9:ServiceLoader
- zafu 1461 (DP)
- 【榜首团队专访】冲刺复赛,他们有绝招
- 支持ie与FireFox的剪切板代码
- 使用maven打包项目执行clean时报错clean报错Failed to clean project
- 开源软件与自由软件的区别
- 两独立样本非参数检验的Mann-whitneyU检验
- BDB 入门篇 第6章 A DPL Example一个DPL 例子
- 快速理解和掌握MIB
- 商汤的AI伴游小精灵(找最多子树)
- 年终盘点 | 用Python分析了上千个基金,终于发现了赚钱的秘密!
- 《数据结构与算法分析》回溯算法之博弈——三连棋(tic tac toe)人机对战AI设计(αβ枝减)
- HDU 6095 Rikka with Competition
- 笔记本 键盘 唤醒计算机,笔记本电脑进入睡眠状态后无法通过鼠标或键盘来唤醒屏幕怎么解决...
热门文章
- 万能五笔输入法导致vs2013 xshell中文乱码
- 军棋单挑布局经验----千局棋手
- 「镁客·请讲」exands卢国鸣:商业WIFI,看似很简单实际非常难
- oracle不用科学计数法,ORACLE中科学计数法显示问题的解决
- 计算机与科学 研究生考试试卷,2018 年全国硕士研究生入学统一考试计算机科学与技术学科联考计算机学科专业基础综合试题及答案...
- 内达培训python多少钱
- 【题目精刷】2022校招大疆创新-数字芯片开发工程师A卷
- 内网穿透、seaFile、NAS私人云盘(一些原理和操作视频网址)
- 273页14万字智慧政务服务一网通办建设方案2022
- 最垃圾的系统之一:广东省自学考试管理系统