ReadWriteLock декоратор, этот поток кода безопасен?

1

Мы создаем хранилища кешей в нашем приложении, поддерживаемые памятью, файлами и удаленными службами. Хотите избежать явной синхронизации, чтобы хранить магазины просто, используя декораторы для поведенческих проблем, таких как блокировка.

Вот простой кеш, это всего лишь пример!

import java.util.HashMap;

public class SimpleCache {

   private HashMap<String,Object> store;  
   private final BlockingCacheDecorator decorator; 

   public SimpleCache(){  
      store = new HashMap<String,Object>();
      decorator = new BlockingCacheDecorator(this);
   }

   //is NOT called directly, always uses decorator
   public Object get(String key){
      return store.get(key);
   }

   //is NOT called directly, always uses decorator 
   public void set(String key, Object value){
      store.put(key, value);
   }

   //is NOT called directly, always uses decorator
   public boolean isKeyStale(String key){
      return !(store.containsKey(key));
   }

   //is NOT called directly, always uses decorator
   public void refreshKey(String key){
      store.put(key, new Object());
   }

   public BlockingCacheDecorator getDecorator(){
      return decorator;
   }
}

getDecorator() возвращает декоратор, обеспечивающий синхронизацию для get() и set(), тогда как isKeyStale() и refreshKey() позволяет декоратору проверять, должен ли обновляться ключ, не зная, почему и как. У меня есть идея для синхронизации декоратора из здесь.

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class BlockingCacheDecorator {

   private SimpleCache delegate;
   private final ReentrantReadWriteLock lock;

   public BlockingCacheDecorator(SimpleCache cache){
      delegate = cache;
      lock = new ReentrantReadWriteLock();
   }

   public Object get(String key){      
      validateKey(key);         
      lockForReading();
      try{
         return delegate.get(key);
      }finally{ readUnlocked(); }
   }

   public void setKey(String key, Object value){
      lockForWriting();
      try{
         delegate.set(key,value);
      }finally{ writeUnlocked(); }      
   }

   protected void validateKey(String key){
      if(delegate.isKeyStale(key)){         
         try{
            lockForWriting();
            if(delegate.isKeyStale(key))
               delegate.refreshKey(key);
         }finally{ writeUnlocked(); }
      }
   }

   protected void lockForReading(){
      lock.readLock().lock();
   }   
   protected void readUnlocked(){
      lock.readLock().unlock();
   }   
   protected void lockForWriting(){
      lock.writeLock().lock();
   }   
   protected void writeUnlocked(){
      lock.writeLock().unlock();
   }  
}

Вопросов:

  • Предполагая, что SimpleCache используется только через его декоратор, безопасен ли код?
  • Является ли неправильная практика для ReadWriteLock вне синхронизируемого класса? SimpleCache.getDecorator() обеспечивает сопоставление 1 к 1 между экземплярами кеша и декоратора, поэтому я предполагаю, что это нормально.
  • 0
    Обычно кеши используются в параллельной среде, поэтому взаимное исключение не является для них чем-то касательным. Нет смысла использовать этот Cache одним потоком. Так почему же вы делаете методы Cache public ? Имхо, более понятно иметь Cache качестве интерфейса, а затем иметь реализацию, которая по умолчанию является поточно-ориентированной. Тогда вы смотрели на Guava CacheBuilder ?
  • 0
    @VictorSorokin У нас есть интерфейс Cache , не показывал, так как он не имеет отношения к вопросу. Почему вы говорите, что этот кеш используется одним потоком? Мы намерены использовать его в параллельной среде, поэтому блокировки чтения позволяют нескольким потокам читать одновременно, не так ли? Я получил идею блокирующего декоратора отсюда: bit.ly/1j5lDvE
Показать ещё 6 комментариев
Теги:
multithreading
synchronization
blocking
reentrantreadwritelock

2 ответа

1
  • Является ли этот код потокобезопасным?

Да. Предполагая, что экземпляр декорированного SimpleCache не передается.

  • Является ли неправильная практика для чтения ReadWriteLock вне синхронизируемого класса? SimpleCache.getDecorator() обеспечивает сопоставление 1 к 1 между экземплярами кеша и декоратора, поэтому я предполагаю, что это нормально.

Нет. Хотя стоит также отметить, что, как обсуждалось в комментариях, BlockingCacheDecorator обычно реализует интерфейс Cache.

  • 0
    Декоратор реализует интерфейс, хотел, чтобы пример был простым, я должен был упомянуть предположения, мой плохой! :-D
1

В его нынешнем виде код тривиально не-потокобезопасен, поскольку ничего не мешает вызывающему SimpleCache напрямую обращаться к методам SimpleCache или передавать один и тот же экземпляр SimpleCache нескольким декораторам, что приводит к еще большему хаосу.

Если вы обещаете никогда не делать этого, это технически потокобезопасно, но мы все знаем, насколько эти обещания стоят.

Если цель состоит в том, чтобы иметь возможность использовать различные реализации базовых кешей, я бы создал интерфейс CacheFactory:

interface CacheFactory {
  Cache newCache();
}

Пример внедрения завода:

class SimpleCacheFactory implements CacheFactory {
  private final String cacheName; //example cache parameter

  public SimpleCacheFactory( String cacheName ) {
    this.cacheName = cacheName;
  }

  public Cache newCache() {
    return new SimpleCache( cacheName );
  }
}

И, наконец, ваш класс делегатов:

public class BlockingCacheDecorator {

   private final Cache delegate;
   private final ReentrantReadWriteLock lock;

   public BlockingCacheDecorator(CacheFactory factory){
     delegate = factory.newCache();
     lock = new ReentrantReadWriteLock();
   }

   //rest of the code stays the same
}

Таким образом, гораздо более надежная гарантия того, что ваши экземпляры Cache не будут повторно использоваться повторно или получить доступ к внешнему агенту. (То есть, если фабрика намеренно неправильно реализована, но по крайней мере ваше намерение не повторять использование экземпляров Cache ясно).

Примечание. Вы также можете использовать анонимный внутренний класс (или, возможно, закрытие) для обеспечения заводской реализации.

  • 0
    Я понимаю, что вы имеете в виду, но не могли бы вы привести краткий пример?
  • 0
    Я обновил свой ответ с большим количеством кода.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню