Simple Java name based locks?

2019-01-02 20:40发布

MySQL has a handy function:

SELECT GET_LOCK("SomeName")

This can be used to create simple, but very specific, name based locks for an application. However, it requires a database connection.

I have many situations like:

someMethod() {
    // do stuff to user A for their data for feature X
}

It doesn't make sense to simply synchronize this method, because, for example, if this method is called for user B in the meantime, user B does not need to wait for user A to finish before it starts, only operations for the user A and feature X combination need to wait.

With the MySql lock I could do something like:

someMethod() {
    executeQuery("SELECT GET_LOCK('userA-featureX')")
    // only locked for user A for their data for feature X
    executeQuery("SELECT RELEASE_LOCK('userA-featureX')")
}

Since Java locking is based on objects, it seems like I would need to create a new object to represent the situation for this lock and then put it in a static cache somewhere so all the threads can see it. Subsequent requests to lock for that situation would then locate the lock object in the cache and acquire its lock. I tried to create something like this, but then the lock cache itself needs synchronization. Also, it is difficult to detect when a lock object is no longer being used so that it can be removed from the cache.

I have looked at the Java concurrent packages, but nothing stands out as being able to handle something like this. Is there an easy way to implement this, or am I looking at this from the wrong perspective?

Edit:

To clarify, I am not looking to create a predefined pool of locks ahead of time, I would like to create them on demand. Some pseudo code for what I am thinking is:

LockManager.acquireLock(String name) {
    Lock lock;  

    synchronized (map) {
        lock = map.get(name);

        // doesn't exist yet - create and store
        if(lock == null) {
            lock = new Lock();
            map.put(name, lock);
        }
    }

    lock.lock();
}

LockManager.releaseLock(String name) {
    // unlock
    // if this was the last hold on the lock, remove it from the cache
}

标签: java locking
22条回答
无色无味的生活
2楼-- · 2019-01-02 21:03

After some disappointment that there is no language level support or simple Guava/Commons class for named locks,

This is what I settled down to:

ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

Object getLock(String name) {
    Object lock = locks.get(name);
    if (lock == null) {
        Object newLock = new Object();
        lock = locks.putIfAbsent(name, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

void somethingThatNeedsNamedLocks(String name) {
    synchronized(getLock(name)) {
        // some operations mutually exclusive per each name
    }
}

Here I achieved: little boilerplate code with no library dependency, atomically acquiring the lock object, not polluting the global interned string objects, no low-level notify/wait chaos and no try-catch-finally mess.

查看更多
高级女魔头
3楼-- · 2019-01-02 21:03

Here is a simple and optimized solution which addresses the removal of used locks also, but with an overhead of synchronization of the Map:

public class NamedLock {
private Map<String, ReentrantLock> lockMap;

public NamedLock() {
    lockMap = new HashMap<>();
}

public void lock(String... name) {
    ReentrantLock newLock = new ReentrantLock(true);
    ReentrantLock lock;
    synchronized (lockMap) {
        lock = Optional.ofNullable(lockMap.putIfAbsent(Arrays.toString(name), newLock)).orElse(newLock);
    }
    lock.lock();
}

public void unlock(String... name) {
    ReentrantLock lock = lockMap.get(Arrays.toString(name));
    synchronized (lockMap) {
        if (!lock.hasQueuedThreads()) {
            lockMap.remove(name);
        }
    }
    lock.unlock();
}    

}

查看更多
几人难应
4楼-- · 2019-01-02 21:04

All those answers I see are way too complicated. Why not simply use:

public void executeInNamedLock(String lockName, Runnable runnable) {
  synchronized(lockName.intern()) {
    runnable.run();
  }
}

The key point is the method intern: it ensures that the String returned is a global unique object, and so it can be used as a vm-instance-wide mutex. All interned Strings are held in a global pool, so that's your static cache you were talking about in your original question. Don't worry about memleaks; those strings will be gc'ed if no other thread references it. Note however, that up to and including Java6 this pool is kept in PermGen space instead of the heap, so you might have to increase it.

There's a problem though if some other code in your vm locks on the same string for completely different reasons, but a) this is very unlikely, and b) you can get around it by introducing namespaces, e.g. executeInNamedLock(this.getClass().getName() + "_" + myLockName);

查看更多
孤独寂梦人
5楼-- · 2019-01-02 21:04

Your idea about a shared static repository of lock objects for each situation is correct.
You don't need the cache itself to be synchronized ... it can be as simple as a hash map.

Threads can simultaneously get a lock object from the map. The actual synchronization logic should be encapsulated within each such object separately (see the java.util.concurrent package for that - http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/package-summary.html)

查看更多
孤独总比滥情好
6楼-- · 2019-01-02 21:05

This thread is old, but a possible solution is the framework https://github.com/brandaof/named-lock.

NamedLockFactory lockFactory = new NamedLockFactory();

...

Lock lock = lockFactory.getLock("lock_name");
lock.lock();

try{
  //manipulate protected state
}
finally{
    lock.unlock();
}
查看更多
只靠听说
7楼-- · 2019-01-02 21:07

Maybe a little later but you can use Google Guava Striped

Conceptually, lock striping is the technique of dividing a lock into many stripes, increasing the granularity of a single lock and allowing independent operations to lock different stripes and proceed concurrently, instead of creating contention for a single lock.

//init
stripes=Striped.lazyWeakLock(size);
//or
stripes=Striped.lock(size);
//...
Lock lock=stripes.get(object);
查看更多
登录 后发表回答