如何在方法参数值上提供同步?
使用“相同”参数值A的所有方法调用都应该同步。具有不同参数值的方法调用,例如即使与A的呼叫已经在等待中,B也可以访问。对B的下一个并发调用还必须等待第一个B被释放。
我的用例:我想在ID级别上同步对JPA实体的访问,但由于我需要某种队列,因此希望避免悲观锁定。用于锁定的“ 密钥”旨在为实体ID -实际上是Java Long类型。
protected void entityLockedAccess(SomeEntity myEntity) {
//getId() returns different Long objects so the lock does not work
synchronized (myEntity.getId()) {
//the critical section ...
}
}
我阅读了有关锁定对象的信息,但不确定如何适合我的情况。 在顶层,我想管理对执行关键代码的应用程序的特定REST调用。
谢谢, 克里斯
答案 0 :(得分:3)
据我了解,您基本上希望为每个SomeEntity
ID设置不同的唯一锁。
您可以使用Map<Integer, Object>
来实现。
您只需将每个ID映射到一个对象。如果已经有一个对象,则可以重用它。看起来可能像这样:
static Map<Integer, Object> locks = new ConcurrentHashMap<>();
public static void main(String[] args)
{
int i1 = 1;
int i2 = 2;
foo(i1);
foo(i1);
foo(i2);
}
public static void foo(int o)
{
synchronized (locks.computeIfAbsent(o, k -> new Object()))
{
// computation
}
}
这将在地图中创建2个锁定对象,因为在第二次i1
调用中重用了foo(i1)
的对象。
答案 1 :(得分:1)
问题是您根本不应该在值(例如字符串或Integer对象)上进行同步。
含义:您将需要在此处定义一些特殊的EntityId类,当然,所有使用相同ID的“数据”在某种程度上都需要使用相同 EntityId对象。
答案 2 :(得分:1)
已合并并可能重用的对象不应用于同步。如果是这样,则可能导致无关的线程因无用的堆栈跟踪而死锁。
具体来说,String
文字和带框的原语(例如Integers
)应不用作锁定对象,因为它们已被池化并可以重复使用。
对于Boolean
对象来说,情况更糟,因为Boolean
,Boolean.TRUE
和Boolean.FALSE
只有两个实例,每个使用布尔值的类都将引用两者之一。
我阅读了有关锁定对象的信息,但是我不确定它们将如何适合我的 案件。在顶层,我想管理对我的特定REST调用 执行关键代码的应用程序。
您的数据库将照顾并发写入和其他事务性问题。 您需要做的就是使用事务。
我还建议您解决经典问题(DIRTY READs NON Repeatable reads)。您也可以将Optimistic Locking用于
答案 3 :(得分:0)
private static final Set<Integer> lockedIds = new HashSet<>();
private void lock(Integer id) throws InterruptedException {
synchronized (lockedIds) {
while (!lockedIds.add(id)) {
lockedIds.wait();
}
}
}
private void unlock(Integer id) {
synchronized (lockedIds) {
lockedIds.remove(id);
lockedIds.notifyAll();
}
}
public void entityLockedAccess(SomeEntity myEntity) throws InterruptedException {
try {
lock(myEntity.getId());
//Put your code here.
//For different ids it is executed in parallel.
//For equal ids it is executed synchronously.
} finally {
unlock(myEntity.getId());
}
}
答案 4 :(得分:0)
只需使用这个类: (并且地图不会随着时间的推移而增加)
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
public class SameKeySynchronizer<T> {
private final ConcurrentHashMap<T, Object> sameKeyTasks = new ConcurrentHashMap<>();
public void serializeSameKeys(T key, Consumer<T> keyConsumer) {
// This map will never be filled (because function returns null), it is only used for synchronization purposes for the same key
sameKeyTasks.computeIfAbsent(key, inputArgumentKey -> acceptReturningNull(inputArgumentKey, keyConsumer));
}
private Object acceptReturningNull(T inputArgumentKey, Consumer<T> keyConsumer) {
keyConsumer.accept(inputArgumentKey);
return null;
}
}
就像在这个测试中:
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class SameKeySynchronizerTest {
private static final boolean SHOW_FAILING_TEST = false;
@Test
void sameKeysAreNotExecutedParallel() throws InterruptedException {
TestService testService = new TestService();
TestServiceThread testServiceThread1 = new TestServiceThread(testService, "a");
TestServiceThread testServiceThread2 = new TestServiceThread(testService, "a");
testServiceThread1.start();
testServiceThread2.start();
testServiceThread1.join();
testServiceThread2.join();
Assertions.assertFalse(testService.sameKeyInProgressSimultaneously);
}
@Test
void differentKeysAreExecutedParallel() throws InterruptedException {
TestService testService = new TestService();
TestServiceThread testServiceThread1 = new TestServiceThread(testService, "a");
TestServiceThread testServiceThread2 = new TestServiceThread(testService, "b");
testServiceThread1.start();
testServiceThread2.start();
testServiceThread1.join();
testServiceThread2.join();
Assertions.assertFalse(testService.sameKeyInProgressSimultaneously);
Assertions.assertTrue(testService.differentKeysInProgressSimultaneously);
}
private class TestServiceThread extends Thread {
TestService testService;
String key;
TestServiceThread(TestService testService, String key) {
this.testService = testService;
this.key = key;
}
@Override
public void run() {
testService.process(key);
}
}
private class TestService {
private final SameKeySynchronizer<String> sameKeySynchronizer = new SameKeySynchronizer<>();
private Set<String> keysInProgress = ConcurrentHashMap.newKeySet();
private boolean sameKeyInProgressSimultaneously = false;
private boolean differentKeysInProgressSimultaneously = false;
void process(String key) {
if (SHOW_FAILING_TEST) {
processInternal(key);
} else {
sameKeySynchronizer.serializeSameKeys(key, inputArgumentKey -> processInternal(inputArgumentKey));
}
}
@SuppressWarnings("MagicNumber")
private void processInternal(String key) {
try {
boolean keyInProgress = !keysInProgress.add(key);
if (keyInProgress) {
sameKeyInProgressSimultaneously = true;
}
try {
int sleepTimeInMillis = 100;
for (long elapsedTimeInMillis = 0; elapsedTimeInMillis < 1000; elapsedTimeInMillis += sleepTimeInMillis) {
Thread.sleep(sleepTimeInMillis);
if (keysInProgress.size() > 1) {
differentKeysInProgressSimultaneously = true;
}
}
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
} finally {
keysInProgress.remove(key);
}
}
}
}