按值同步而不是按对象同步



我实现了一个简单的锁定解决方案,它为一个值而不是对象创建一个锁,并且想知道专家对可能的性能或安全缺陷的意见。我们的想法是将其用于帐户余额更新,获取唯一帐号的锁。

下面是一个实现:

import java.util.*;
public class Mutex<T> {
private final Set<T> set = new HashSet();
public synchronized Lock acquireLock(
T value
) throws InterruptedException {
while(!set.add(value)) {
this.wait();
}
return new Lock(value);
}
public class Lock {
private final T value;
public Lock(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void release() {
synchronized(Mutex.this) {
set.remove(value);
Mutex.this.notifyAll();
}
}
}
}

下面是检查可操作性的示例用法:

public class Test {
private Mutex mutex = new Mutex();
public static void main(String[] args) {
Test test = new Test();
Thread t1 = new Thread(() -> {
try {
test.test("SameValue");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
t1.setName("Thread 1");
Thread t2 = new Thread(() -> {
try {
test.test("SameValue");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
t2.setName("Thread 2");
t1.start();
t2.start();
}
public void test(String value)
throws
InterruptedException {
Lock lock = mutex.acquireLock(value);
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName());
} finally {
lock.release();
}
}
}

关于你的实现,

我会使用Set而不是List来保存您的值(我假设值有适当的等号/哈希码来实现这一点):List#contains方法是在O(n)中,如果您同时使用了很多IBAN,则可能会很昂贵。

同样,您应该避免使用synchronize(this)(它与synchronized关键字on方法相同)。

为了解决你的问题,我使用这样的语句:

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Locks<T> {
private final Lock lock = new ReentrantLock();
//a Bimap from guava might be better here if you have the dependency
//in your project
private final Map<Reference<?>, T> valuePerReference = new HashMap<>();
private final Map<T, Reference<Lock>> locks = new HashMap<>();
private final ReferenceQueue<Lock> lockReferenceQueue = new ReferenceQueue<>();
public Locks() {
final Thread cleanerThread = new Thread(new Cleaner());
cleanerThread.setDaemon(true);
cleanerThread.start();
}
/**
* @param value the value the synchronization must be made on
* @return a lock that can be used to synchronize block of code.
*/
public Lock getLock(T value) {
lock.lock();
try {
return getExistingLock(value).orElseGet(() -> createNewLock(value));
} finally {
lock.unlock();
}
}

private Optional<Lock> getExistingLock(T value) {
return Optional.ofNullable(locks.get(value)).map(Reference::get);
}
private Lock createNewLock(T value) {
//I create ReentrantLock here but a Supplier<Lock> could be a parameter of this
//class to make it more generic. Same remark for SoftReference below.
final Lock lock = new ReentrantLock();
final Reference<Lock> reference = new SoftReference<>(lock, lockReferenceQueue);
this.locks.put(value,reference);
this.valuePerReference.put(reference,value);
return lock;
}

private void removeLock(Reference<?> reference) {
lock.lock();
try {
final T value = valuePerReference.remove(reference);
locks.remove(value);
} finally {
lock.unlock();
}
}

private class Cleaner implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
final Reference<? extends Lock> garbaged = lockReferenceQueue.remove();
removeLock(garbaged);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}

然后像这样使用:

import java.util.concurrent.locks.Lock;
public class Usage {
private final Locks<String> locks = new Locks<>();

public void doSomethind(String iban) {
final Lock lock = locks.getLock(iban);
lock.lock();
try {
//.. do something with your iban
} finally {
lock.unlock();
}
}
}

虽然它使用ReentrantLock,但代码可以很容易地修改为ReadWriteLock。

最新更新