java - Java中如何优化并发操作?

标签 java multithreading

我对 Java 中的多线程仍然很犹豫。我在这里描述的内容是我的申请的核心,我需要正确对待这一点。该解决方案需要快速运行并且需要实际上安全。这行得通吗?欢迎任何建议/批评/替代解决方案。

<小时/>

我的应用程序中使用的对象的生成成本较高,但很少更改,因此我将它们缓存在 *.temp 文件中。一个线程可能尝试从缓存中检索给定的对象,而另一个线程则尝试在缓存中更新该对象。检索和存储的缓存操作封装在 CacheService 实现中。

考虑这种情况:

Thread 1: retrieve cache for objectId "page_1".
Thread 2: update cache for objectId "page_1".
Thread 3: retrieve cache for objectId "page_2".
Thread 4: retrieve cache for objectId "page_3".
Thread 5: retrieve cache for objectId "page_4".

注意:线程 1 似乎检索了一个过时的对象,因为线程 2 拥有该对象的较新副本。这完全没问题,所以我不需要任何逻辑来赋予线程 2 优先级。

如果我在服务上同步检索/存储方法,那么我会不必要地减慢线程 3、4 和 5 的速度。多个检索操作在任何给定时间都将有效,但很少会调用更新操作。这就是为什么我想避免方法同步。

我认为我需要同步线程 1 和 2 专用的对象,这意味着锁定对象注册表。在这里,一个明显的选择是 Hashtable,但同样,Hashtable 上的操作是同步的,所以我正在尝试 HashMap。该映射存储一个字符串对象,用作同步的锁定对象,键/值将是正在缓存的对象的 id。因此,对于对象“page_1”,键将是“page_1”,锁定对象将是值为“page_1”的字符串。

如果我的注册表正确,那么我还想保护它不被太多条目淹没。我们不详细讨论原因。我们假设,如果注册表增长超过了定义的限制,则需要使用 0 个元素重新初始化。对于不同步的 HashMap 来说这是有一点风险的,但是这种洪水将超出正常的应用程序操作范围。这应该是非常罕见的情况,希望永远不会发生。但既然有可能,我就想保护自己免受它的侵害。

@Service
public class CacheServiceImpl implements CacheService {
    private static ConcurrentHashMap<String, String> objectLockRegistry=new ConcurrentHashMap<>();

public Object getObject(String objectId) {
    String objectLock=getObjectLock(objectId);
    if(objectLock!=null) {
        synchronized(objectLock) {
            // read object from objectInputStream
    }
}

public boolean storeObject(String objectId, Object object) {
    String objectLock=getObjectLock(objectId);

    synchronized(objectLock) {
        // write object to objectOutputStream
    }
}

private String getObjectLock(String objectId) {
    int objectLockRegistryMaxSize=100_000;

    // reinitialize registry if necessary
    if(objectLockRegistry.size()>objectLockRegistryMaxSize) {
        // hoping to never reach this point but it is not impossible to get here
        synchronized(objectLockRegistry) {
            if(objectLockRegistry.size()>objectLockRegistryMaxSize) {
                objectLockRegistry.clear();
            }
        }
    }

    // add lock to registry if necessary
    objectLockRegistry.putIfAbsent(objectId, new String(objectId));

    String objectLock=objectLockRegistry.get(objectId);
    return objectLock;
}

最佳答案

如果您从磁盘读取数据,锁争用不会成为性能问题。

您可以让两个线程获取整个缓存的锁,进行读取,如果值丢失,则释放锁,从磁盘读取,获取锁,然后如果值仍然丢失,则写入它,否则返回现在存在的值。

您遇到的唯一问题是并发读取会破坏磁盘...但是操作系统缓存会很热,因此磁盘不应被过度破坏。

如果这是一个问题,请将您的缓存切换为保存 Future<V>代替<V>

get 方法将变成这样:

public V get(K key) {
    Future<V> future;
    synchronized(this) {
        future = backingCache.get(key);
        if (future == null) {
            future = executorService.submit(new LoadFromDisk(key));
            backingCache.put(key, future);
        }
    }
    return future.get();
}

是的,这是一个全局锁...但是您正在从磁盘读取,并且在经过证明的性能瓶颈之前不会进行优化...

哦。第一个优化,将 map 替换为ConcurrentHashMap并使用putIfAbsent而且你根本就没有锁! (但只有当您知道这是一个问题时才这样做)

关于java - Java中如何优化并发操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13555723/

相关文章:

java - 从文件中删除某些数据

java - 拦截私有(private)字段访问进行延迟加载

java - SOAP HTTPS 测试获取类转换异常(字符串到整数)

multithreading - Perl线程获得迭代速度

multithreading - Qt 线程在调用 exit/quit 后不会停止

java - 使用非自动生成的 ID 保存在 JPA/Hibernate 中的替代方法

Java构造函数继承?

Java 线程池 - 哪种类型和多少?

python - 如何在 kivy-python 中使用带多线程的时钟对象更新进度条?

c# - 在异步线程中创建对象会导致 STA 异常