Java:在原语上同步?

时间:2010-02-17 15:59:10

标签: java concurrency

在我们的系统中,我们有一个方法,当用某个ID调用它时会做一些工作:

public void doWork(long id) { /* ... */ }

现在,可以针对不同的ID同时完成这项工作,但是如果使用2个线程使用相同的ID调用该方法,则一个线程应该阻塞直到它完成。

最简单的解决方案是将Map从Long ID映射到我们可以锁定的任意对象。我预见到的一个问题是,我们可以在系统中拥有大量的ID,而且这张地图每天都会不断增长。

理想情况下,我认为我们需要一个系统,我们每个线程都会获取一个锁定对象,在可能的情况下锁定,完成工作,然后发出信号表明我们已完成锁定。如果很明显没有其他人使用这个特定的锁,那么请从锁定图中安全地将其删除以防止内存泄漏。

我想这一定是一个非常常见的场景,所以我希望有一个现有的解决方案。有人知道吗?

11 个答案:

答案 0 :(得分:15)

我不久前为自己发明了类似的东西。我将其称为等价类锁,意思是,它锁定了与给定事物相等的所有事物。您可以获取它from my github,并根据Apache 2许可证使用它,如果您愿意,或者只是阅读并忘记它!

答案 1 :(得分:8)

您可以尝试使用ReentrantLock,以便拥有Map<Long,Lock>。现在在lock.release()之后你可以测试lock.hasQueuedThreads()。如果返回false,则可以从Map中删除它。

答案 2 :(得分:6)

你可以尝试下面的小'黑客'

String str = UNIQUE_METHOD_PREFIX + Long.toString(id);
synchornized(str.intern()) { .. }

100%保证返回相同的实例。

UNIQUE_METHOD_PREFIX可以是硬编码常量,也可以使用:

获得
StackTraceElement ste = Thread.currentThread().getStackTrace()[0];
String uniquePrefix = ste.getDeclaringClass() + ":" +ste.getMethodName();

这将保证锁定只发生在这种精确的方法上。这是为了避免死锁。

答案 3 :(得分:4)

我会说你已经有了解决方案。让懒惰的LockManager和引用计数管理这些锁。然后在doWork

中使用它
public void doWork(long id) {
    LockObject lock = lockManager.GetMonitor(id);
    try {
        synchronized(lock) {
            // ...
        }
    } finally {
        lock.Release();
    }
}

答案 4 :(得分:4)

首先:

  1. 您无法锁定基元和
  2. 除非你小心如何构建它们,否则不要锁定Long。自动装箱或Long.valueOf()在一定范围内创建的长值在整个JVM中保证相同,这意味着其他线程可以锁定在同一个精确的Long对象上并为您提供串扰。这可能是一个微妙的并发错误(类似于锁定实习生的字符串)。
  3. 你在这里谈论锁定条带设置。连续体的一端是所有ID的单个巨型锁,这将是简单且安全的,但不是并发的。另一端是每个id的锁定,这很容易(在某种程度上)并且安全且非常并发,但可能需要在内存中有大量“可锁定对象”(如果您还没有它们)。在中间的某个地方是为一系列ID创建锁的想法 - 这使您可以根据您的环境调整并发性,并选择内存和并发之间的权衡。

    ConcurrentHashMap可用于实现此目的,因为CHM由段(子映射)内部组成,每个段有一个锁。这使您的并发性等于段的数量(默认为16但可配置)。

    还有许多其他可能的解决方案可用于对ID空间进行分区并创建锁定集,但您对清理和内存泄漏问题敏感是正确的 - 在保持并发性的同时处理这一问题是一件棘手的事情。您需要对每个锁使用某种引用计数,并仔细管理旧锁的驱逐,以避免驱逐锁定过程中的锁。如果你走这条路线,使用ReentrantLockReentrantReadWriteLock(而不是在对象上同步),因为这可以让你明确地将锁作为一个对象进行管理,并使用它上面的额外方法。

    Java Concurrency in Practice第11.4.3节中还有一些内容和StripedMap示例。

答案 5 :(得分:0)

使用java.util.concurrent包中的SynchronizedHashMap或Collections.synchronizedMap(Map m)而不是使用不同步的检索和插入调用的普通HashMap是不够的?

类似的东西:

Map<Long,Object> myMap = new HashMap<Long,Object>();
Map<Long,Object> mySyncedMap=Collections.synchronizedMap(myMap);

答案 6 :(得分:0)

  

过早优化是邪恶的根源

使用(同步)地图尝试。

如果它变得太大,你可以定期清除它的内容。

答案 7 :(得分:0)

您可以创建一个列表或一组活动ID,并使用wait和notify:

List<Long> working;
public void doWork(long id) {
synchronized(working)
{
   while(working.contains(id))
   {
      working.wait();
   }
   working.add(id)//lock
}
//do something
synchronized(working)
{
    working.remove(id);//unlock
    working.notifyAll();
}
}

问题解决了:

  • 只有具有相同id的线程等待,其他所有线程都是并发的
  • 没有内存泄漏,因为“锁定”(长)将在解锁时删除
  • 使用自动装箱

那里的问题:

  • 而/ notifyAll可能会因线程数过多而导致性能下降
  • 不可重入

答案 8 :(得分:0)

这是我使用规范化地图的地方,它会获取您的long输入并返回一个规范的Long对象,然后您可以使用该对象进行同步。我写过关于规范化地图here的文章;只需将String替换为Long(为了让您的生活更轻松,请让long作为参数)。

一旦你有了规范化的地图,你就会写下这样的锁定守护代码:

Long lockObject = canonMap.get(id);
synchronized (lockObject)
{
    // stuff
}

规范化地图将确保为相同的ID返回相同的lockObject。当没有对lockObject的活动引用时,它们将有资格进行垃圾回收,因此您不会用不必要的对象填充内存。

答案 9 :(得分:0)

我可能会迟到游戏,但是此解决方案不会泄漏任何内存,并且您不必记住要进行任何锁定释放:

Synchronizer<AccountId> synchronizer = new Synchronizer();

...

// first thread - acquires "lock" for accountId accAAA

synchronizer.synchronizeOn(accountId("accAAA"), () -> {
    long balance = loadBalance("accAAA")
    if (balance > 10_000) {
        decrementBalance("accAAA", 10_000)
    }
})

...

// second thread - is blocked while first thread runs (as it uses the same "lock" for accountId accAAA)

synchronizer.synchronizeOn(accountId("accAAA"), () -> {
    long balance = loadBalance("accAAA")
    if (balance > 2_000) {
        decrementBalance("accAAA", 2_000)
    }
})

...

// third thread - won't be blocked by previous threads (as it is for a different accountId)

synchronizer.synchronizeOn(accountId("accXYZ"), () -> {
    long balance = loadBalance("accXYZ")
    if (balance > 3_500) {
        decrementBalance("accXYZ", 3_500)
    }
})

要使用它,您只需添加一个依赖项:

compile 'com.github.matejtymes:javafixes:1.3.0'

答案 10 :(得分:-2)

我建议您使用java.util.concurrent中的实用程序,尤其是AtomicLong类。见related javadoc