防止基于键并发执行java方法

问题描述 投票:0回答:2

我正在开发一个java应用程序,需要向外部API提交post请求。我遇到的问题是,如果执行该方法的事件被触发多次(例如用户意外双击),我们最终会向外部系统提交重复的数据。

我需要一种在调用方法时存储标识键(例如 ID)的方法,然后防止使用该键进行任何其他调用,直到当前执行完成。

供参考,我的函数如下所示:

public static SendCreateMessageResponse createMessage(String baseUrl, String application, String env, String type, String channel, String modoToken, Message message, ArrayList<ChannelDatum> personalChannels) {


            PersonalMessageRequest pemr = null;

            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);
            
        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = null;
        ObjectMapper om = new ObjectMapper();

        Response response = null;
        try {
            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();
//Note that this is not asynchronous. execute will block until the response returns or //fails
            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 {
            if(response!=null) {
                if(response.body()!=null) {

                        response.body().close();
                }               
            }
        }       
        return null;
    }
java concurrency locking
2个回答
0
投票

多次触发(例如用户不小心双击

禁用按钮

只需在第一次单击时禁用按钮即可。任务完成后,启用按钮。

发布到队列

如果您特定选择的 GUI 框架存在点击泄漏问题,则每次点击都可能会发布到队列中。

每次单击时,通过

Runnable
 方法将一个对象(可能是 Callable
/
offer
)发布到容量受限的队列。

如果队列的容量限制设置为 1,则对第二个元素调用

offer
将被拒绝,返回
false

在第一个对象完成工作之前,不要从队列中删除该对象。

服务器端

如果您确实无法从源头(客户端)解决问题,您可以将每个帖子收集到过期的缓存中。当服务器收到帖子时,它首先检查缓存是否匹配。如果匹配,则拒绝/忽略该帖子。如果不匹配,则将该帖子添加到缓存中,然后处理该帖子。

您会在 Google Guava 中找到过期的缓存实现,也许在 Eclipse Collections 中。显然 Spring 还提供了缓存以及生存时间功能。

您可以编写自己的后台线程来重复清除缓存。但我建议使用已经编写、测试和部署的库中的现有库。

两种处理过期缓存的方法均在以下位置讨论:带有过期键的 Java 基于时间的映射/缓存


0
投票

这种模式被称为

debouncing
。您可以用 java 编写一个简单的防抖器,由
ScheduledExecutorService
支持,它可以在缓存中保留一段时间。

public class Debouncer {
   private final long duration;
   private final TimeUnit unit;
   private final ScheduledExecutorService executor;
   private final ConcurrentMap<Object, Future<Object>> cache = new ConcurrentHashMap<>();
   
   // constructor

   public Object debounce(Object key, Callable<Object> callable) throws Exception {
      Function<Object, Future<Object>> function = k -> {
          Future<Object> future = executor.submit(callable);
          executor.schedule(() -> cache.remove(k), duration, unit);
          return future;
      };
      Future<Object> future = cache.computeIfAbsent(key, function);
      return future.get();
   }
}

假设您有以下 API

public interface Api {
   String methodOne(String arg1, String arg2);
   Date methodTwo(Long arg1);
}

你可以用

包裹它
public class DebouncingApi implements Api {
   private final Api delegate;
   private final Debouncer debouncer;

   // constructor

   public String methodOne(String arg1, String arg2) {
      String key = List.of(arg1, arg2);
      return (String) debouncer.debounce(key, () -> delegate.methodOne(arg1, arg2));
   }

   public Date methodTwo(Long arg1) {
      return (Date) debouncer.debounce(arg1, () -> delegate.method2(arg1));
   }
}
© www.soinside.com 2019 - 2024. All rights reserved.