Как правильно синхронизировать общий статический объект в Java? - PullRequest
2 голосов
/ 09 июня 2010

Это вопрос относительно того, как правильно синхронизировать общий объект в Java.Одно предостережение: доступ к объекту, которым я хочу поделиться, должен осуществляться из статических методов.Мой вопрос: если я синхронизирую на статическом поле, блокирует ли этот класс класс, к которому относится поле, подобно тому, как это делает синхронизированный статический метод?Или это будет блокировать только само поле?

В моем конкретном примере я спрашиваю: вызовет ли PayloadService.getPayload () или PayloadService.setPayload () блокировку PayloadService.payload?Или он заблокирует весь класс PayloadService?

public class PayloadService extends Service {   


private static PayloadDTO payload = new PayloadDTO();


public static  void setPayload(PayloadDTO payload){
    synchronized(PayloadService.payload){
        PayloadService.payload = payload;
    }
}

public static  PayloadDTO getPayload() {
    synchronized(PayloadService.payload){
        return PayloadService.payload ;
    }
}

...

Это правильный / приемлемый подход?

В моем примере PayloadService представляет собой отдельный поток, обновляющий объект полезной нагрузки через регулярные промежутки времени - другие потоки должны вызывать PayloadService.getPayload () через случайные интервалы для получения последних данных, и мне нужно убедиться, что онине блокируйте PayloadService от выполнения своей задачи таймера

На основании полученных ответов я рефакторинг сделал следующее:

public class PayloadHolder {

private static PayloadHolder holder;    
private static PayloadDTO payload;

private PayloadHolder(){        
}

public static synchronized PayloadHolder getInstance(){
    if(holder == null){
        holder = new PayloadHolder();
    }
    return holder;
}

public static synchronized void initPayload(){      
    PayloadHolder.payload = new PayloadDTO();       
}
public static synchronized PayloadDTO getPayload() {
    return payload;
}
public static synchronized void setPayload(PayloadDTO p) {
    PayloadHolder.payload = p;
}

}

public class PayloadService extends Service {   

  private static PayloadHolder payloadHolder = PayloadHolder.getInstance();

  public static  void initPayload(){        
            PayloadHolder.initPayload();        
  }

  public static  void setPayload(PayloadDTO payload){       
        PayloadHolder.setPayload(payload);      
  }

  public static  PayloadDTO getPayload() {      
    return PayloadHolder.getPayload();      
  }

     ...

Законен ли этот подход?Мне также любопытно, лучше ли это делать таким образом или использовать подход AtomicReference, упомянутый Hardcoded ...?- Я держу экземпляр PayloadHolder на PayloadService просто для того, чтобы поддерживать ссылку на класс PayloadHolder активным в jvm до тех пор, пока работает PayloadService.

Ответы [ 6 ]

3 голосов
/ 09 июня 2010

Ваш код должен выглядеть следующим образом:

public static  void setPayload(PayloadDTO payload){
    synchronized(PayloadService.class){
        PayloadService.payload = payload;
    }
}

public static  PayloadDTO getPayload() {
    synchronized(PayloadService.class){
        return PayloadService.payload ;
    }
}

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

Обновление, ответ на комментарий johnrock: Блокировка всего класса является проблемой только при наличии другой синхронизированной статическойблоки, которые вы хотите запустить в настоящее время.Если вы хотите иметь несколько независимых заблокированных разделов, я предлагаю вам сделать что-то вроде этого:

public static final Object myLock = new Object();

public static  void setPayload(PayloadDTO payload){
    synchronized(myLock){
        PayloadService.payload = payload;
    }
}

public static  PayloadDTO getPayload() {
    synchronized(myLock){
        return PayloadService.payload ;
    }
}

Или, если вам требуется более сложный шаблон параллелизма, посмотрите java.util.concurrent в котором есть много готовых классов, чтобы помочь вам.

1 голос
/ 09 июня 2010

Вы можете, как упоминалось в других постах, синхронизироваться в классе или на явном мониторе.

Есть 2 других способа, если мы предполагаем, что вы используете sychnronize только для поточно-ориентированного получения и установки свойства: volatile и AtomicReference .

летучий

Ключевое слово volatile сделает доступ к переменной атомарным, что означает, что чтение и присвоение переменной не будут оптимизированы локальными регистрами ЦП и выполняются атомарно.

AtomicReference

AtomicReference - это специальный класс в пакете java.util.concurrent.atomic , который обеспечивает атомарный доступ к ссылкам, подобным переменным. Это очень похоже на volatile, но дает вам некоторые дополнительные элементарные операции, такие как compareAndSet.

Пример:

public class PayloadService extends Service {   

private static final AtomicReference<PayloadDTO> payload 
          = new AtomicReference<PayloadDTO>(new PayloadDTO());

public static void setPayload(PayloadDTO payload){
    PayloadService.payload.set(payload);
}

public static PayloadDTO getPayload() {
    return PayloadService.payload.get ;
}

Edit:

Ваш Holder выглядит довольно смущенным, поскольку вы создаете экземпляры классов только для вызова статических методов. Попытка исправить это с помощью AtomicReference:

public class PayloadHolder {

  private static AtomicReference<PayloadHolder> holder = new AtomicReference<PayloadHolder();    

  //This should be fetched through the holder instance, so no static
  private AtomicReference<PayloadDTO> payload = new AtomicReference<PayloadDTO>();

  private PayloadHolder(){        
  }

  public static PayloadHolder getInstance(){
    PayloadHolder instance = holder.get();

    //Check if there's already an instance
    if(instance == null){

      //Try to set a new PayloadHolder - if no one set it already.
      holder.compareAndSet(null, new PayloadHolder());
      instance = holder.get();

    }
    return instance;
  }

  public void initPayload(){      
    payload.set(new PayloadDTO());

    //Alternative to prevent a second init:
    //payload.compareAndSet(null, new PayloadDTO());
  }

  public PayloadDTO getPayload() {
    return payload.get;
  }

  public void setPayload(PayloadDTO p) {
    payload.set(p);
  }

}

public class PayloadService extends Service {   

  private final PayloadHolder payloadHolder = PayloadHolder.getInstance();

  public void initPayload(){        
    payloadHolder.initPayload();        
  }

  public void setPayload(PayloadDTO payload){       
    payloadHolder.setPayload(payload);      
  }

  public PayloadDTO getPayload() {      
    return payloadHolder.getPayload();      
  }
}
1 голос
/ 09 июня 2010

Синхронизация на другом статическом объекте, который не изменяется:

public class PayloadService extends Service {   


private static PayloadDTO payload = new PayloadDTO();

private static final Object lock = new Object();


public static  void setPayload(PayloadDTO payload){
    synchronized(lock){
        PayloadService.payload = payload;
    }
}

public static  PayloadDTO getPayload() {
    synchronized(lock){
        return PayloadService.payload ;
    }
}
1 голос
/ 09 июня 2010

Это правильный / приемлемый подход?

Нет, причина этого в том, что вы никогда не должны синхронизироваться с переменной / полем, которое может изменить ее значение. То есть, когда вы синхронизируете на PayloadService.payload и устанавливаете новый PayloadService.payload, вы нарушаете золотое правило синхронизации.

Вы должны либо выполнить синхронизацию на экземпляре класса, либо создать произвольный private static final Object lock = new Object() и выполнить синхронизацию на этом. Вы получите тот же эффект, что и синхронизация в классе.

1 голос
/ 09 июня 2010

Мой вопрос: если я синхронизирую на статическом поле, блокирует ли этот класс класс, к которому относится поле, подобно тому, как это делает синхронизированный статический метод? Или это будет блокировать только само поле?

Нет, он просто блокирует сам объект (атрибут класса, а не весь класс)

Это правильный / приемлемый подход?

Возможно, вы могли бы взглянуть на пакет java.util.concurrent.lock .

Мне не очень нравится синхронизация по атрибуту класса, но я думаю, что это просто вопрос.

0 голосов
/ 09 июня 2010

Существует большая часть функциональности в этом классе, которая не опубликована, что способствует тому, является ли этот подход поточно-ориентированным: как вы получаете доступ к экземпляру PayloadDTO из других частей этого класса, где он находитсяused?

Если вы предоставляете методы, которые могут поменяться в другом экземпляре на payload, когда другой поток выполняет код, использующий объект payload, то это не безопасно для потока.

Например, если у вас есть метод execute(), который выполняет основную работу этого класса и вызывает методы для payload, вам необходимо убедиться, что один поток не может изменить экземпляр payload с помощью метода setter, пока другой поток занятвыполняется execute().

Короче говоря, когда у вас есть общее состояние, вам нужно синхронизировать все операции чтения и записи в этом состоянии.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...