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;
}