代码之家  ›  专栏  ›  技术社区  ›  Tim Frey

Java:在原语上同步?

  •  13
  • Tim Frey  · 技术社区  · 15 年前

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

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

    现在,这项工作可以针对不同的ID同时进行,但是如果两个线程用相同的ID调用该方法,那么一个线程应该阻塞,直到完成为止。

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

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

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

    10 回复  |  直到 15 年前
        1
  •  15
  •   Jonathan Feinberg    15 年前

    前段时间我为自己发明了这样的东西。我称之为等价类锁,意思是,它锁定所有与给定事物相等的事物。你可以得到它 from my github ,并根据Apache2许可证使用它,如果您愿意的话,也可以直接阅读并忘记它!

        2
  •  8
  •   John Vint    15 年前

    你可以尝试使用可重入锁,这样你就有了 Map<Long,Lock> . 现在,在lock.release()之后,可以测试lock.hasQueuedThreads()。如果返回false,您可以将其从映射中删除。

        3
  •  7
  •   Bozho    15 年前

    你可以试试下面的小技巧

    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();
    

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

        4
  •  4
  •   Alex Miller    15 年前

    首先:

    1. 不要长时间锁定,除非你在构建它们时很小心。由autoboxing或Long.valueOf()在特定范围内创建的长值在整个JVM中保证是相同的,这意味着其他线程可能会锁定在同一个确切的长对象上,并给您带来串扰。这可能是一个微妙的并发错误(类似于锁定内部字符串)。

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

    对于划分ID空间和创建锁集,还有许多其他可能的解决方案,但您对清理和内存泄漏问题非常敏感,在维护并发性的同时处理这些问题是一项棘手的工作。您需要对每个锁使用某种引用,并小心地管理旧锁的逐出,以避免逐出正在被锁定的锁。如果你走这条路,使用 ReentrantLock ReentrantReadWriteLock

    还有一些关于这个的东西和一个StripedMap的例子 Java Concurrency in Practice

        5
  •  4
  •   David Schmitt    15 年前

    我想说你已经很快找到解决办法了。制造 LockManager doWork :

    public void doWork(long id) {
        LockObject lock = lockManager.GetMonitor(id);
        try {
            synchronized(lock) {
                // ...
            }
        } finally {
            lock.Release();
        }
    }
    
        6
  •  0
  •   fasseg    15 年前

    比如:

    Map<Long,Object> myMap = new HashMap<Long,Object>();
    Map<Long,Object> mySyncedMap=Collections.synchronizedMap(myMap);
    
        7
  •  0
  •   chburd    15 年前

    用(同步的)地图试试看。

        8
  •  0
  •   kdgregory    15 年前

    在这里,我会使用一个规范化映射,它将 long 输入并返回规范 Long 对象,然后可以使用该对象进行同步。我写过规范化地图的文章 here ; 简单地替换 String 通过 长的 (为了让你的生活更轻松,让它花点时间 作为参数)。

    一旦有了规范化映射,就可以这样编写锁保护代码:

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

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

        9
  •  0
  •   josefx    15 年前

    您可以创建一个列表或一组活动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();
    }
    }
    

    解决的问题:

    • 无内存泄漏,因为“锁”(长)将在解锁时移除

    存在的问题:

        10
  •  0
  •   Matej Tymes    6 年前

    我可能会在游戏中迟到,但此解决方案不会泄漏任何内存,而且您不必记住执行任何锁释放:

    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'