【问题标题】:Java: lock functions which work on the same elementJava:在同一元素上工作的锁定函数
【发布时间】:2012-11-02 18:42:31
【问题描述】:

有许多线程在同一张地图上的元素上工作,每个线程都有一个代码,例如:

THREAD_1 works on an element with code "1234"
THREAD_2 works on an element with code "1234"
THREAD_3 works on an element with code "9876"
etc...

元素不是永久性的,即 THREAD_1 可能会删除元素“1234”然后重新插入。我想要的是,当 THREAD_1 正在处理元素“1234”(也将其删除)时,THREAD_2 必须等待。

有办法吗?

一种可能的解决方案可能是在 HashMap 中插入一个假元素,然后在该元素上使用“已同步”子句强制同步。你有什么想法? (很明显,如果线程使用相关代码删除了元素,该假元素也会保留在地图中)...

【问题讨论】:

    标签: java synchronization locking synchronized


    【解决方案1】:

    鉴于您的特定问题,没有一个 java 标准对象可以解决您的所有问题。这是一个我认为是正确的解决方案,并且不会在您的锁定映射中保留任何不必要的键或值:

    // we don't use a ConcurrentHashMap, because we have some other operations 
    // that need to be performed in atomically with map.put and map.remove.
    // ConcurrentHashMap would of course also work, but it doesn't remove the 
    // need for external synchronization in in our case.
    Map<String, CountingLock> locksMap = new HashMap<String, CountingLock>();
    ...
    
    HttpResponse myFunction(String key) {
    
        CountingLock lock;
        synchronized(locksMap){
            lock = locksMap.get(key);
            if(lock == null){
                lock = new CountingLock();
                locksMap.put(key, lock);
            }
            lock.prepare(); // has to be done while holding the lock of locksMap.
                            // basically tells other threads that the current 
                            // thread intends to acquire the lock soon. This way,
                            // the other threads know not to remove this lock 
                            // from locksMap as long as another one has indicated
                            // that he is going to need it soon.
        }
    
        lock.lock(); // has to be done while NOT holding the lock of locksMap,
                     // or we risk deadlock situations.
    
        try {
            // ...
            // work
            // ...
        } finally {
            synchronized(locksMap) {
                if(lock.unlock() == 0){
                    // no other thread is intending to use this lock any more. 
                    // It is safe to remove it from the map. The next thread 
                    // will just have to recreate a new lock for the same key.
                    locksMap.remove(key);
                }
            }
        }
    
        return SOMETHING;    
    }
    
    private static class CountingLock {
        // The number of threads that are trying to access the protected Key
        private AtomicInteger interestedThreads = new AtomicInteger(0);
    
        private Lock lock = new ReentrantLock();
    
        public void prepare(){
            interestedThreads.incrementAndGet();
        }
    
        public void lock(){
            lock.lock();
        }
    
        public int unlock(){
            lock.unlock();
            return interestedThreads.decrementAndGet();              
        }
    }
    

    此代码在所有情况下都应按预期工作。这是一个有趣的问题要解决:-)

    【讨论】:

    • 这个解决方案应该可以工作。只是整个地图上的同步子句不太好,这可能是一个瓶颈。不过谢谢,我认为到目前为止这是最好的解决方案......奇怪的是不存在命名锁......
    • 它应该不会成为太大的瓶颈,因为锁内的所有操作都非常快。由于您显然正在使用一些与 HTTP 相关的东西,我怀疑您在“工作”块中所做的任何事情的执行时间都是同步块的内容的很多倍。因此,这些同步块不应该对性能产生任何可衡量的影响。
    • 我同意你的看法。它有效(显然)并且它似乎不是一个大瓶颈;)谢谢!
    【解决方案2】:

    我们使用一种我们称之为 LockMap 的东西。

    LockMap 本质上是:

    Map<Object, ReadWriteLock>
    

    我们有一个同步方法来获取特定对象的锁。

    由于地图依赖于等价性,而不是同一性,因此equal() 的两个对象会为您提供相同的锁。

    所以:

    lock1 = map.get(new Thing(1));
    lock2 = map.get(new Thing(1));
    
    lock1 == lock2 = true
    

    这很方便。

    获得锁后,您可以根据需要锁定它以控制对对象的访问。

    我们做的另一件事是使用 LRU 映射(使用 LinkedHashMap -- 参见 this),这样旧的对象键在未使用时会从末端脱落。

    【讨论】:

      【解决方案3】:

      你应该使用ConcurrentHashMap

      支持检索的完全并发和可调整的预期更新并发的哈希表。

      检索操作(包括 get)一般不会阻塞,因此可能会与更新操作(包括 put 和 remove)重叠。

      更新操作之间允许的并发性由可选的 concurrencyLevel 构造函数参数(默认 16)指导,该参数用作内部大小调整的提示。该表在内部进行了分区,以尝试允许指定数量的并发更新而不会发生争用。因为在哈希表中的放置本质上是随机的,所以实际并发会有所不同

      【讨论】:

      • 是的,我用过。但问题是这样的:每个元素都有一个关联的计数器,并且可以将这些元素存储在永久内存中(例如文件)。我需要确保,如果有人正在处理该元素(可以在内存中或文件中),则没有人可以访问它。然后,我需要一个通用的方法......
      • @Massimo 使用一个类,该类将在包含 AutomicBoolean 的地图中作为值,如果有人正在处理它,则将被标记,然后您将不会删除它也不会返回值。
      • 那你说的是我的解决方案?问题是:我怎样才能避免这张地图无限增长?有人应该删除无用的元素...
      • @Massimo 不,不会。因为一旦你将 flag 设置为 false,你只能在那个实例中删除那个元素,这样它就不会无限增长
      • 问题是我需要在没有AutomicBoolean的Android上做这些操作... :(
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多