Java同步取决于方法参数

时间:2018-07-09 08:48:51

标签: java rest synchronization synchronized

如何在方法参数值上提供同步?

使用“相同”参数值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调用。

谢谢, 克里斯

5 个答案:

答案 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对象来说,情况更糟,因为BooleanBoolean.TRUEBoolean.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());
        }
    }
  • id 不仅可以是'Integer',而且可以是任何具有正确覆盖的'equals''hashCode'方法的类。
  • li>
  • 最终尝试-非常重要-即使操作引发异常,您也必须保证在操作后解锁等待线程。
  • 如果您的后端分布在多个服务器/ JVM 中,则将无法使用。

答案 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);
            }
        }
    }
}