依赖于方法参数的Java同步

依赖于方法参数的Java同步,java,rest,synchronization,synchronized,Java,Rest,Synchronization,Synchronized,如何根据方法参数值提供同步 应同步使用“相同”参数值A的所有方法调用。具有不同参数值(例如B)的方法调用可以访问,即使具有A的调用已经在等待。对B的下一个并发调用也必须等待释放第一个B 我的用例:我想同步ID级别上对JPA实体的访问,但希望避免悲观锁定,因为我需要某种队列。用于锁定的“键””旨在成为实体ID——实际上是Java Long类型 protected void entityLockedAccess(SomeEntity myEntity) { //getId() returns

如何根据方法参数值提供同步

应同步使用“相同”参数值A的所有方法调用。具有不同参数值(例如B)的方法调用可以访问,即使具有A的调用已经在等待。对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调用,该应用程序执行关键代码

谢谢,
Chris

问题在于您不应该对值(例如字符串或对象)进行同步


意思:您需要在这里定义一些特殊的EntityId类,当然,所有使用相同ID的“数据”都需要使用相同的EntityId对象

不应将合并并可能重用的对象用于同步。如果是,则会导致不相关的线程死锁,并产生无用的堆栈跟踪

具体来说,
String
文本和装箱原语(如
整数
等)应用作锁对象,因为它们被合并和重用

对于
Boolean
对象,情况更糟,因为只有
Boolean
的两个实例,
Boolean.TRUE
Boolean.FALSE
,并且每个使用Boolean的类都将引用其中一个

我读过关于锁对象的文章,但我不确定它们是否适合我的应用程序 案例在顶层,我想管理一个特定的REST调用 执行关键代码的应用程序

您将处理并发写入和其他事务性问题。 您所需要做的就是使用事务


我还建议你学习经典问题()。您也可以使用for

据我所知,您基本上希望为每个
SomeEntity
ID使用不同的、唯一的锁

您可以通过
地图
实现这一点

您只需将每个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
    }
}
静态映射锁=新的ConcurrentHashMap();
公共静态void main(字符串[]args)
{
inti1=1;
int i2=2;
foo(i1);
foo(i1);
foo(i2);
}
公共静态无效foo(int o)
{
已同步(locks.computeIfAbsent(o,k->new Object())
{
//计算
}
}
这将在映射中创建2个锁对象,因为在第二个
foo(i1)
调用中重用了
i1
的对象。

private static final Set lockedds=new HashSet();
    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());
        }
    }
私有无效锁(整数id)引发InterruptedException{ 已同步(lockedds){ 而(!lockedds.add(id)){ lockedds.wait(); } } } 私有无效解锁(整数id){ 已同步(lockedds){ lockedds.remove(id); lockedds.notifyAll(); } } public void entityLockedAccess(SomeEntity myEntity)引发InterruptedException{ 试一试{ 锁(myEntity.getId()); //把你的代码放在这里。 //对于不同的ID,它是并行执行的。 //对于相同的ID,它是同步执行的。 }最后{ 解锁(myEntity.getId()); } }
  • id不仅可以是“整数”,还可以是具有正确重写的'equals''hashCode'方法的任何类
  • 最后重试-非常重要-您必须保证在操作后解锁等待的线程,即使操作引发异常
  • 如果您的后端分布在多个服务器/JVM上,那么它将不起作用
只需使用此类: (地图的大小不会随时间而增加)

import java.util.concurrent.ConcurrentHashMap;
导入java.util.function.Consumer;
公共类SameKeySynchronizer{
private final ConcurrentHashMap sameKeyTasks=新ConcurrentHashMap();
公共密钥(T密钥、消费者密钥消费者){
//此映射永远不会被填充(因为函数返回null),它仅用于同一密钥的同步目的
sameKeyTasks.ComputeFabSent(key,inputArgumentKey->acceptReturningNull(inputArgumentKey,keyConsumer));
}
私有对象acceptReturningNull(T inputArgumentKey,Consumer keyConsumer){
接受(inputArgumentKey);
返回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);
            }
        }
    }
}
import java.util.Set;
导入java.util.concurrent.ConcurrentHashMap;
导入org.junit.jupiter.api.Assertions;
导入org.junit.jupiter.api.Test;
类SameKeySynchronizerTest{
私有静态最终布尔值SHOW_failed_TEST=false;
@试验
void sameKeySarenoteExecutedParallel()引发InterruptedException{
TestService TestService=新的TestService();
TestServiceThread testServiceThread1=新的TestServiceThread(testService,“a”);
TestServiceThread testServiceThread2=新的TestServiceThread(testService,“a”);
testServiceThread1.start();
testServiceThread2.start();
testServiceThread1.join();
testServiceThread2.join();
assertFalse(testService.sameKeyInProgressSynchronized);
}
@试验
void differentKeysAreExecutedParallel()引发InterruptedException{
TestService TestService=新的TestService();
TestServiceThread
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);
            }
        }
    }
}