【问题标题】:Java concurrency - How to synchronize on method parameterJava并发 - 如何在方法参数上同步
【发布时间】:2020-10-14 02:42:42
【问题描述】:

我有一个与 Java 中的同步和并发相关的问题。

所以我有一个方法,像这样:

private boolean loadData(final Integer fundId, List<Trade> trades) {
        synchronized (fundId) {
            // do lots of things here and finally load the trades into DB
        }
    }

在我做这个改变之前,完整的方法loadData是同步的private synchronized boolean loadData。但是,我的要求是,如果说,fundId - 1 正在处理,那么我可以允许并发处理除 1 之外的任何其他fundId。

因此,上面的代码也不起作用,因为锁将在 Integer 对象上,因此不能同时处理其他fundId。 有没有办法根据方法参数实现并发处理?

【问题讨论】:

    标签: java java-8 concurrency parallel-processing synchronization


    【解决方案1】:

    您需要在ConcurrentHashMap 中为fundId 的每个值创建一个条目才能锁定它。

       static Map<Integer, Object> locks = new ConcurrentHashMap<>();
        
       private boolean loadData(final Integer fundId, List<Trade> trades){
            locks.computeIfAbsent(fundId, k-> { /* your actual job */ return null; });
            }
        }
    

    希望有帮助!

    【讨论】:

    • locks里面的对象永远不会被删除,在长时间运行的过程中可能会消耗掉整个内存。
    • 请看我的评论。 “一旦您终止处理,不要忘记释放锁定的fundId”。释放锁意味着删除条目,因此内存中没有对象。
    • 感谢您的帮助,这与@k1r0 的建议非常相似。之后会尝试并更新你们:-)再次感谢您的帮助。
    • 这与synchronized (fundId) 有何不同?它解决了什么问题?
    • 感谢@Holger 的评论。这确实非常有帮助,感谢 M.Mas 更新答案
    【解决方案2】:

    正如它所写的那样,该函数将在对象fundId 上同步,而不是在Integer 上同步。因此,如果您从具有相同 fundId 实例的另一个线程调用相同的函数,它将阻塞。但是,如果您将其与其他 fundId 实例一起调用,则无论值如何,它都不会同步。

    如果您需要基于一个值进行同步,您可以使用一组共享的整数(即fundId)。在集合上同步,并尝试插入整数。如果它已经在那里,其他人正在处理该值,所以你等待。如果不存在,则插入、解锁、处理、再次锁定、移除值并发出信号。

    【讨论】:

      【解决方案3】:

      您可以通过多种方式实现这一目标:

      • 如果包含loadData() 的类被称为FundLoader,您可以有一个Map&lt;Integer, FundLoader&gt; fundLoaders,并且每个FundLoader 负责加载给定fundId 的交易。同步将再次在 loadData 的方法级别上进行
      • 在 loadData 中进行自定义同步 更新 - 添加了 fundWaitingForLock 以防止锁已从 fundLocks 映射中获取的情况
      private final Map<Integer, Object> fundLocks = new HashMap<>();
      private final Map<Integer, AtomicInteger> fundsWaitingForLock = new HashMap<>();
      
      private boolean loadData(final Integer fundId, final List<String> trades) {
          Object lock;
          synchronized (fundLocks) {
              lock = fundLocks.computeIfAbsent(fundId, id -> new Object());
              fundsWaitingForLock.computeIfAbsent(fundId, id -> new AtomicInteger()).incrementAndGet();
          }
          synchronized(lock) {
              try {
                  // do lots of things here and finally load the trades into DB
                  return true;
              } finally {
                  synchronized (fundLocks) {
                      if (fundsWaitingForLock.get(fundId).decrementAndGet() == 0) {
                          fundLocks.remove(fundId);
                          fundsWaitingForLock.remove(fundId);
                      }
                  }
              }
          }
      }
      
      • 传递锁而不是fundId。
      private boolean loadData(final Lock fundIdLock, final List<String> trades) {
          fundIdLock.lock();
          try {
              // do lots of things here and finally load the trades into DB
          } finally {
              fundIdLock.unlock();
          }
          return true;
      }
      

      【讨论】:

      • 谢谢第一个和第二个想法好像很不错。我可以试试。
      • 这与synchronized (fundId) 有何不同?它解决了什么问题?
      • 这受制于与ConcurrentHashMap 变体see here 相同的竞争条件。 computeIfAbsent 将返回已经包含的对象(如果有的话),因此第一个线程的 remove(fundId) 可以删除已经被第二个(或任意数量的线程)使用的对象。
      • 没错,我为等待锁的线程数添加了一个计数器,只有当它达到 0 时才会删除。
      猜你喜欢
      • 2022-12-10
      • 1970-01-01
      • 2018-12-16
      • 2023-03-25
      • 1970-01-01
      • 2013-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多