Java: Synchronizing on primitives?

前端 未结 10 1828
小鲜肉
小鲜肉 2021-02-01 21:13

In our system, we have a method that will do some work when it\'s called with a certain ID:

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

Now, this

相关标签:
10条回答
  • 2021-02-01 22:02

    You could create a list or set of active ids and use wait and 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();
    }
    }
    

    Problems solved:

    • Only threads with same id's wait, all others are concurrent
    • No memory Leak as the "locks" (Long) will be removed on unlock
    • Works with autoboxing

    Problems there:

    • while/notifyAll may cause some performance loss with high number of threads
    • Not reentrant
    0 讨论(0)
  • 2021-02-01 22:06

    You can try something with a ReentrantLock, such that you have a Map<Long,Lock>. Now after lock.release() You can test lock.hasQueuedThreads(). If that returns false you can remove it from the Map.

    0 讨论(0)
  • 2021-02-01 22:10

    I might be late to the game, but this solution isn't leaking any memory and you don't have to remember to do any lock releases:

    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)
        }
    })
    

    to use it you just add a dependency:

    compile 'com.github.matejtymes:javafixes:1.3.0'
    
    0 讨论(0)
  • 2021-02-01 22:11

    This is where I would use a canonicalizing map, which takes your long input and returns a canonical Long object that you can then use to synchronize. I've written about canonicalizing maps here; simply replace String by Long (and to make your life easier, let it take a long as a parameter).

    Once you have the canonicalizing map, you'd write your lock-guarded code like this:

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

    The canonicalizing map would ensure that the same lockObject is returned for the same ID. When there are no active references to lockObject, they will be eligible for garbage collection, so you won't fill up memory with needless objects.

    0 讨论(0)
提交回复
热议问题