如何在多线程 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 个并行线程中的相同订单相关,那么我们不能 运行 并行交易。
是否有任何优雅有效的方法来确保仅与一个订单相关的交易不会 运行 并行而所有其他交易都是多线程的?
[更新]:添加了使用 WeakHashMap-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) {
// ...
}
工作完成后别忘了取下钥匙。
要使用垃圾收集来删除未使用的键,您可以使用 WeakHashMap 结构:
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
当你只读顺序时,你锁定了读锁,这样多个线程只使用那个顺序不会互相阻塞。只有一个线程修改顺序,其他线程才会被阻塞。
我之前也回答过一个读写锁的实现:
遗留代码如下:
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 个并行线程中的相同订单相关,那么我们不能 运行 并行交易。
是否有任何优雅有效的方法来确保仅与一个订单相关的交易不会 运行 并行而所有其他交易都是多线程的?
[更新]:添加了使用 WeakHashMap-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) {
// ...
}
工作完成后别忘了取下钥匙。
要使用垃圾收集来删除未使用的键,您可以使用 WeakHashMap 结构:
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
当你只读顺序时,你锁定了读锁,这样多个线程只使用那个顺序不会互相阻塞。只有一个线程修改顺序,其他线程才会被阻塞。
我之前也回答过一个读写锁的实现: