基于标识键防止Java方法并发执行的解决方案需求
解决重复POST请求提交问题
核心思路
通过线程安全的请求标识跟踪机制,在请求发起前记录唯一业务标识键,请求执行完成(含异常场景)后移除该键;同一标识的重复请求会被直接拦截,避免向外部API提交重复数据。
实现步骤
- 用线程安全容器存储正在处理的请求标识,确保多线程环境下的原子性操作
- 基于业务参数生成唯一请求标识(如消息ID、关键参数组合哈希)
- 在方法入口校验标识:已存在则拦截重复请求,不存在则标记为处理中
- 请求结束后无论成功/失败,都清理标识,避免内存泄漏
修改后的代码
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class MessageService { // 替换为你的实际类名 // 线程安全Map,跟踪正在处理的请求标识 private static final ConcurrentMap<String, Boolean> PROCESSING_REQUESTS = new ConcurrentHashMap<>(); public static SendCreateMessageResponse createMessage(String baseUrl, String application, String env, String type, String channel, String modoToken, Message message, ArrayList<ChannelDatum> personalChannels) { // 生成唯一请求标识:优先用message的业务ID,无ID则组合关键参数生成 String requestKey = message.getId(); // 假设message有getId()方法,按需调整 if (requestKey == null) { // 备选方案:组合标题、内容、渠道生成唯一键 requestKey = String.format("%s_%s_%s", message.getTitle(), message.getBody(), channel); // 或用哈希值缩短键长度:requestKey = Integer.toString((message.getTitle() + message.getBody() + channel).hashCode()); } // 原子性检查并标记请求:已存在则拦截 if (!PROCESSING_REQUESTS.putIfAbsent(requestKey, Boolean.TRUE)) { logger.info("拦截重复请求,请求标识:{}", requestKey); return null; // 可替换为抛出自定义异常,根据业务需求调整 } PersonalMessageRequest pemr = null; OkHttpClient client = new OkHttpClient(); MediaType mediaType = MediaType.parse("application/json"); RequestBody body = null; ObjectMapper om = new ObjectMapper(); Response response = null; try { pemr = new PersonalMessageRequest(); PersonalMessage pm = new PersonalMessage(); pm.setTitle(message.getTitle()); pm.setBody(message.getBody()); logger.debug("Send message to modo: " + pm); pemr.setPersonal_message(pm); if(pmr!=null) { logger.debug(om.writeValueAsString(pmr)); body = RequestBody.create(mediaType, om.writeValueAsString(pmr)); } logger.info("Calling URL: "+baseUrl+application+"/"+env+"/"+type+"/channels/"+channel+"/messages"); logger.trace("Sending data: " + body); Request request = new Request.Builder() .url(baseUrl+application+"/"+env+"/"+type+"/channels/"+channel+"/messages") .method("POST", body) .addHeader("accept", "application/vnd.modo.communicate.v2") .addHeader("Content-type", "application/json") .addHeader("Authorization", "Bearer "+modoToken) .build(); response = client.newCall(request).execute(); String jsonResponse = response.body().string(); logger.debug(jsonResponse); SendCreateMessageResponse messagesResponse = om.readValue(jsonResponse, SendCreateMessageResponse.class); return messagesResponse; } catch (IOException e) { logger.error("Error creating message", e); } finally { // 无论请求结果如何,都清理标识,避免内存泄漏 PROCESSING_REQUESTS.remove(requestKey); // 关闭响应体资源 if(response!=null && response.body()!=null) { try { response.body().close(); } catch (IOException e) { logger.error("Failed to close response body", e); } } } return null; } }
关键细节说明
- 线程安全保障:使用
ConcurrentHashMap的putIfAbsent方法,原子性完成"检查-添加"操作,避免多线程竞态条件 - 标识唯一性:优先使用业务天然唯一的ID(如消息ID),无ID时组合核心业务参数生成,确保同一业务请求的标识唯一
- 资源清理:在finally块中移除标识,保证异常场景下也能清理,避免容器内存泄漏
- 拦截逻辑:重复请求直接返回null,也可抛出
DuplicateRequestException等自定义异常,更清晰地反馈给调用方
内容的提问来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

