【问题标题】:Fine-grained synchronization/locking of method calls based on method parameters基于方法参数的方法调用的细粒度同步/锁定
【发布时间】:2013-05-30 15:01:59
【问题描述】:

我想根据一些 id 同步方法调用,例如给定对象实例的并发装饰器。
例如:
所有使用参数“id1”调用该方法的线程都应该彼此串行执行。
其余所有调用具有不同参数(例如“id2”)的方法的所有线程都应与使用参数“id1”调用该方法的线程并行执行,但又彼此串行执行。

所以在我看来,这可以通过为每个此类方法参数设置一个锁 (http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLock.html) 实例来实现。 每次使用参数调用方法时,都会查找与特定参数值(例如“id1”)对应的锁实例,并且当前线程会尝试获取锁。

用代码说话:

public class ConcurrentPolicyWrapperImpl implements Foo {

private Foo delegate;

/**
* Holds the monitor objects used for synchronization.
*/
private Map<String, Lock> concurrentPolicyMap = Collections.synchronizedMap(new HashMap<String, Lock>());

/**
* Here we decorate the call to the wrapped instance with a synchronization policy.
*/
@Override
public Object callFooDelegateMethod (String id) {
        Lock lock = getLock(id);
        lock.lock();
        try {
            return delegate.delegateMethod(id);
        } finally {
            lock.unlock();
        }
}


protected Lock getLock(String id) {
        Lock lock = concurrentPolicyMap.get(id);

        if (lock == null) {
            lock = createLock();
            concurrentPolicyMap.put(id, lock);

        }
        return lock;
    }

}

protected Lock createLock() {
        return new ReentrantLock();
    }

这似乎可行——我用 jmeter 等做了一些性能测试。 尽管如此,我们都知道 Java 中的并发是一件棘手的事情,所以我决定在这里征求您的意见。

我无法停止思考是否有更好的方法来实现这一点。例如,通过使用 BlockingQueue 实现之一。你怎么看?

我也无法确定获取锁是否存在潜在的同步问题,即protected Lock getLock(String id) 方法。我正在使用同步集合,但这是否足够? IE。它不应该是类似于以下内容,而不是我目前拥有的内容:

protected Lock getLock(String id) {
  synchronized(concurrentPolicyMap) {
    Lock lock = concurrentPolicyMap.get(id);

    if (lock == null) {
      lock = createLock();
      concurrentPolicyMap.put(id, lock);

    }
    return lock;
  }

}

你们觉得呢?

【问题讨论】:

    标签: java concurrency locking


    【解决方案1】:

    除了锁创建问题之外,该模式还可以,只是您可能拥有无限数量的锁。通常人们通过创建/使用条带锁来避免这种情况。 guava 库中有一个很好/简单的实现。

    Application area of lock-striping

    How to acquire a lock by a key

    http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Striped.html

    使用 guava 实现的示例代码:

    private Striped<Lock> STRIPPED_LOCK = Striped.lock(64);
    
    public static void doActualWork(int id) throws InterruptedException {
        try {
            STRIPPED_LOCK.get(id).lock();
            ...
        } finally {
            STRIPPED_LOCK.get(id).unlock();
        }
    }
    

    【讨论】:

    • 您是在说“锁创建问题放在一边”...您是否发现了任何问题,或者您的意思是您没有对此发表评论?
    • 现在更仔细地看一下这个问题——是的,你会在你的 getLock() 方法中定义需要同步块。正如您在第二个示例中所拥有的那样。否则,图像 (a) 5 个线程同时调用 getLock(),用于相同的 ID; (b) 他们都发现锁为空; (c) 它们都创建并返回一个新的锁实例。失败。第二段代码中的同步块避免了这种情况。
    • 谢谢你,基思。顺便说一句,您是否知道如何使用执行者/任务来做类似的事情。 IE。有一个 ThreadPoolExecutor 但通过一个键对任务执行进行分区。例如,键“id1”的任务将以串行方式彼此运行,同时与键“id2”、“id3”等的所有其他任务并行运行......
    • 回答我自己的评论... 这是一篇关于这个主题的好文章:javaspecialists.eu/archive/Issue206.html 在同一篇文章中提到,Executor javadocs 中对此有一些提示,即 SerialExecutor。
    • @Keith 您是否完全确定您的实现,因为您没有对锁对象进行强引用,而是在 finally 块中再次引用 STRIPED_LOCK?我问,因为使用lazyWeakLock 意味着在没有强引用时可以回收锁(似乎不管它的锁状态如何)。因此,我认为您的示例代码可能不是线程安全的。
    【解决方案2】:

    虽然我个人更喜欢 Keith 建议的 Guava 的 Striped&lt;Lock&gt; 方法,但为了讨论和完整性,我想指出,使用动态代理或更通用的 AOP(面向方面​​编程)是一种方法.

    因此,我们将定义一个IStripedConcurrencyAware 接口,作为您想要的“类似于并发装饰器的东西”,并且基于此接口的动态代理/AOP 方法劫持会将方法调用解复用到适当的Executor / Thread.

    我个人不喜欢 AOP(或大部分 Spring,就此而言),因为它破坏了 Core Java 所见即所得的简单性,而是 YMMV。

    【讨论】:

      猜你喜欢
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-16
      • 1970-01-01
      相关资源
      最近更新 更多