如何在多线程 JAVA 环境中保护对象而不损失性能?

How to protect an object in multithreaded JAVA environment without losing performance?

遗留代码如下:

private static final ReentrantLock RE_ENTRANT_LOCK = new ReentrantLock(true);

private void newRunTransaction(final OrderPayment payment, final Address billingAddress, final String command)  {

    TransactionResponse response = null;
    RE_ENTRANT_LOCK.lock();
    try {
        SoapServerPortType client = getClient();
....

我们认为方法开头的锁是多余的,因为我们应该能够 运行 在多个线程中处理事务。另一方面,如果 OrderPayment 与 2 个并行线程中的相同订单相关,那么我们不能 运行 并行交易。

是否有任何优雅有效的方法来确保仅与一个订单相关的交易不会 运行 并行而所有其他交易都是多线程的?

[更新]:添加了使用 Wea​​kHashMap-Cache 的解决方案,让垃圾收集器清理未使用的锁。它们是在这里开发的:Iterating a WeakHashMap

如果payment有一个order的引用并且等同的orders是同一个对象(order1 == order2 <=> order1 is the same as order2),你可以使用同步块:

synchronized(payment.getOrder()) {
    try  {
       // ...
    }
}

警告:您应该确保 payment.getOrder() 不会产生 null 或在这种情况下使用虚拟对象。

编辑:如果 order1 == order2 不成立可能的解决方案:

您可以尝试为订单的相同标识符持有唯一锁:

static Map<Long, Object> lockCache = new ConcurrentHashMap<>();

并在方法中

Object lock = new Object();
Object oldlock = lockCache.putIfAbsent(payment.getOrder().getUid(), lock);
if (oldlock != null) {
    lock = oldlock;
}

synchronized(lock) {
    // ...
}

工作完成后别忘了取下钥匙。

要使用垃圾收集来删除未使用的键,您可以使用 Wea​​kHashMap 结构:

private static Map<Long, Reference<Long>> lockCache = new WeakHashMap<>();

public static Object getLock(Longi)
{
    Long monitor = null;
    synchronized(lockCache) {
        Reference<Long> old = lockCache.get(i);
        if (old != null)
            monitor = old.get();

        // if no monitor exists yet
        if (monitor == null) {
            /* clone i for avoiding strong references 
               to the map's key besides the Object returend 
               by this method.
            */ 
            monitor = new Long(i);
            lockCache.remove(monitor); //just to be sure
            lockCache.put(monitor, new WeakReference<>(monitor));
        }

    }

    return monitor;
}

当您需要更复杂的东西(如重入锁)时,您可以使用以下解决方案的变体:

private static Map<Long, Reference<ReentrantLock>> lockCache = new WeakHashMap<>();
private static Map<ReentrantLock, Long> keyCache = new WeakHashMap<>();

public static ReentrantLock getLock(Long i)
{
    ReentrantLock lock = null;
    synchronized(lockCache) {
        Reference<ReentrantLock> old = lockCache.get(i);
        if (old != null)
            lock = old.get();

        // if no lock exists or got cleared from keyCache already but not from lockCache yet
        if (lock == null || !keyCache.containsKey(lock)) {
            /* clone i for avoiding strong references 
               to the map's key besides the Object returend 
               by this method.
           */ 
            Long cacheKey = new Long(i); 
            lock = new ReentrantLock();
            lockCache.remove(cacheKey); // just to be sure
            lockCache.put(cacheKey, new WeakReference<>(lock));
            keyCache.put(lock, cacheKey);
        }                
    }

    return lock;
}

给每个OrderPayment分配一个读写锁怎么样? http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html

当你只读顺序时,你锁定了读锁,这样多个线程只使用那个顺序不会互相阻塞。只有一个线程修改顺序,其他线程才会被阻塞。

我之前也回答过一个读写锁的实现: