2013-08-09 2 views
6

У меня есть сервер несколько, как это:Потоковый сейф, но быстрый доступ к «окончательной конечной» переменной?

class Server { 

    private WorkingThing worker; 

    public void init() { 
     runInNewThread({ 
      // this will take about a minute 
      worker = new WorkingThing(); 
     }); 
    } 

    public Response handleRequest(Request req) { 
     if (worker == null) throw new IllegalStateException("Not inited yet"); 
     return worker.work(req); 
    } 

} 

Как вы можете видеть, есть потоки обработки запросов и поток initing сервер. Запросы могут появиться до завершения инициализации, поэтому есть чек с IllegalStateException.

Теперь, чтобы сделать эту поточно (так обработчик запросов потоки не видим несвежие, null -значные, версию worker только после инициализации), я должен был бы сделать рабочий изменчивы, синхронизировать на нем, или некоторые например.

Однако после завершения инициализации worker больше не изменится, так что это действительно окончательный вариант. Поэтому кажется, что любой конфликт блокировок, который может произойти, будет пустой тратой. Итак, что самое эффективное, что я могу сделать здесь?

Теперь я знаю, что это практически не имеет значения в практическом смысле (при всем тяжелом подъеме чтения сетевого запроса и т. Д., Что имеет дело с одним замком?), Но я хотел бы узнать из любопытства ,

+0

с использованием ключевого слова «synchronized» стоит много циклов. Я думаю, что лучшим способом было бы сделать переменную 'worker'' static'. Таким образом, он выделит зарезервированное пространство в памяти, и я думаю, что потоки всегда будут смотреть на это пространство. Просто мысль, возможно, неверна. –

+0

Является ли обращение к потоку 'worker.work (req)' safe? Я просто прошу убедиться, что вам требуется только блокировать вызовы потоков до тех пор, пока «worker» не будет инициализирован. Это верно? –

+0

@ViktorSeifert Да, однажды «рабочий» там, он потокобезопасен. –

ответ

3

Заметка об изменчивости: обозначение переменной изменчивости дешевле, чем использование синхронизации (она не включает блокировки) и, как правило, достаточно дешева, чтобы вы не заметили. В частности, на архитектуре x86 чтение изменчивой переменной не стоит больше, чем чтение нелетучей переменной. Однако запись в энергозависимое устройство является более дорогостоящим, и тот факт, что переменная является изменчивой, может помешать некоторым оптимизации компилятора.

Так что использование volatile - это, вероятно, вариант, который дает вам наилучшее соотношение производительности и сложности в вашем сценарии.

У вас нет такого количества альтернатив. В конце концов это сводится к обеспечению безопасной публикации вашего работника. И безопасные идиомы публикации включают в себя:

  • инициализацию экземпляра из статического инициализатора
  • маркировки ссылки на экземпляр в качестве окончательного
  • маркировки ссылки на экземпляр в качестве летучего
  • синхронизирующего всех доступы

В вашем случае доступны только последние два варианта, и использование изменчивости более эффективно.

+0

О, я думал, что волатильность была более или менее синтаксическим сахаром для «придать этой переменной блокировку и всегда синхронизировать ее при доступе к этой переменной». Приятно знать, что это не так. –

+0

Обратите внимание, что статическая инициализация выполняется в синхронизированном блоке; таким образом гарантируется видимость (и другие свойства). – erickson

1

Вы должны объявить работника volatile.

Из-за двух причин

  1. если вы не объявляете его как изменчивы, чем из-за изменения структуры и видимости эффектов вы могли видеть ненулевым ссылка на неполный построенный объект Worker. и, следовательно, вы можете иметь нежелательные эффекты. Этого не произойдет, если вы сделаете своего работника неизменным, используя все конечные переменные .

  2. Теоретически возможно, что ваш основной поток может не увидеть ненулевую ссылку вашего рабочего объекта в течение длительного времени. Поэтому избегайте этого.

Таким образом, если рабочий является неизменным, чем и для пункта 2, вы должны сделать его изменчивым. Всегда избегайте непредсказуемых результатов. Объявив, что он изменчивый, он позаботится об этих проблемах.

0

Я хотел бы использовать ava.util.concurrent.atomic.AtomicReference , если это null, iether бросить или ждать и повторить попытку (если init достаточно быстр).

1

Использовать простой замок для первоначального запроса:

public synchronized void init() { 
    if(worker!=null) return; 
    runInNewThread({ 
     synchronized(Server.this){ 
      worker = new WorkingThing(); 
      Server.this.notify(); 
     } 
    }); 
    this.wait(); 
} 

public Response handleRequest(Request req) { 
    if (worker == null) synchronized(this) { 
     this.wait(); 
    } 
    return worker.work(req); 
} 

Это работает, потому что есть точки синхронизации между доступом к рабочим.

+1

Никогда не выходите за пределы цикла. Если вы пропустите сигнал уведомления, вы можете ждать вечно. И может быть более одного потока ожидания => использовать notifyAll. И нет смысла ждать в конце init. – assylias

+0

Argh, справа - должна быть проверена с помощью двойного замка в блоке synchronized (this) handleRequest. –