2012-06-25 5 views
5

Я запутался в синхронизации метода экземпляра и статического метода. Я хочу написать Потокобезопасный класс следующим образом:Синхронизация по статическому методу и экземпляру

public class safe { 

    private final static ConcurrentLinkedQueue<Object> objectList= 
     new ConcurrentLinkedQueue<Object>(); 

    /** 
    * retrieves the head of the object and prints it 
    */ 
    public synchronized static void getHeadObject() { 
     System.out.println(objectList.peek().toString()); 

    } 

    /** 
    * creates a new object and stores in the list. 
    */ 
    public synchronized void addObject() { 
     Object obj=new Object(); 
     objectList.add(obj); 

    } 
} 

Synchronizing на статический методе зафиксирует на safe.class замок и синхронизацию по методу экземпляра зафиксируется на этом .AND, следовательно, несогласованное состояние будет достигнуто ,

Если я хочу достичь согласованного состояния для фрагмента кода ниже, как это может быть достигнуто?

+1

Почему 'addObject' метод экземпляра? Почему бы не статично? Почему вы все равно синхронизируете вокруг параллельного объекта? Они уже потокобезопасны. – Wug

+0

Что вы подразумеваете под непоследовательным состоянием? –

+0

Я предполагаю, что он имел в виду 'Queue objectList = новый Список ' вместо 'ConcurrentLinkedQueue objectList = new ConcurrentLinkedQueue ' поэтому мы получаем ответ на фактический вопрос, который он задает. –

ответ

1

EDIT: Я предполагаю, что вы имели в виду Queue<Object> objectList вместо ConcurrentLinkedQueue<Object> objectList. ConcurrentLinkedQueue<Object> уже выполняет всю вашу безопасность нитей для вас, то есть вы можете позвонить по номеру objectList.peek(), чтобы вы не беспокоились о состоянии гонки. Это здорово, если вы разрабатываете многопоточные программы, но не настолько хороши для изучения безопасности потоков.

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

public static void getHeadObject() { 
    synchronized(safe.objectList) { 
     System.out.println(objectList.peek().toString()); 
    } 
} 

Это блокирует objectList и не позволяет ему быть считан или записан в любом другом потоке, как программа находится внутри блока синхронизации. Сделайте то же самое для всех других методов: synchronized.

Примечание:

Однако, так как вы делаете только один простой операции GET List.peek(), вы действительно не нужно синхронизировать по objectList, так как в состоянии гонки, он будет получать либо одно значение List или другой. Проблема с условиями гонки заключается в том, что выполняются несколько сложных операций чтения/записи с изменением значения между ними.

Например, если у вас есть класс PairInt с PairInt.x и PairInt.y полей, с тем ограничением, что x = 2y, и вы хотели бы сделать

System.out.println(myIntPair.x.toString() + ", " + myIntPair.y.toString()); 

и другой поток обновлял значение x и y в в то же время,

myIntPair.y = y + 3; 
myIntPair.x = 2 * y; 

И запись нити модифицированного myIntPair между вашим чтения потока myIntPair.x.toString() и myIntPair.y.toString() вы можете получить вывод, который выглядит как (10, 8), что означает, что если вы работаете при условии, что x == 2 * y может привести к краху вашей программы.

В этом случае ваше чтение необходимо использовать synchronized, но и для более простых вещей, как peek() на простом object, который добавляется или удаляется, не изменяется, а в очереди, то synchronized может, в большинстве случаев быть опущен. Фактически, для string, int, bool и т.п. условие для простого чтения следует отбросить.

Однако записи должны всегда быть synchronized для операций, которые явно не являются потокобезопасными, то есть уже обрабатываются java. И как только вы приобретаете более одного ресурс, или требовать, чтобы ваш ресурс остается неизменным в течение всей операции, как вы делаете несколько строк логики к нему, то вы ДОЛЖНЫ ИСПОЛЬЗОВАТЬsynchronized

+0

Он уже использует синхронизированное ключевое слово в объявлении 'getHeadObject', которое просто синхронизируется на safe.class. – Wug

+0

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

+0

Это не помогает ... два безопасных объекта получат доступ к одному и тому же объектуList, и если вы синхронизируете оба объекта, 'synchronized' позволит вам писать с обоими в одно и то же время, потому что вы не явно используя (насколько Vm может сказать), тот же самый объект. –

2

Во-первых, ConcurrentLinkedQueue не требует явного синхронизации. См. this answer.

Во-вторых, вы всегда можете синхронизировать объект, который вы обращаетесь:

public class safe { 

     private final static ConcurrentLinkedQueue<Object> objectList= 
      new ConcurrentLinkedQueue<Object>(); 

     /** 
     * retrieves the head of the object and prints it 
     */ 
    public static void getHeadObject() { 
     synchronized(objectList){ 
      System.out.println(objectList.peek().toString()); 
     } 

    } 

     /** 
     * creates a new object and stores in the list. 
     */ 
    public void addObject() { 
      Object obj=new Object(); 
     synchronized(objectList){ 
      objectList.add(obj); 
     } 

    } 
} 
+0

@assylias: "copy-paste", извините =) –

0

Несколько комментариев:

  • Java конвенции:
    • имена классов должны быть в CamelCase (т.е. назвать свой класс Safe, а не safe)
    • static до synchronized в методах декларации
    • static предшествует final в полях декларации
  • , как другие уже говорили, ConcurrentLinkedQueue уже поточно, поэтому нет необходимости в синхронизации в данном примере вы даете.
  • смешивание статических и нестационарных методов, как вы выглядите странно.
  • Предполагая, что ваш фактический прецедент более сложный, и вам нужен метод запуска атомных операций, тогда ваш код не работает, как вы указали, поскольку два синхронизированных метода не синхронизируются на одном мониторе:
public static synchronized getHeadObject(){} //monitor = Safe.class 
public static synchronized addObject(){} //monitor = this 

так, чтобы ответить на ваш конкретный вопрос, вы могли бы использовать отдельный статический объект как замок:

public class Safe { 

    private static final ConcurrentLinkedQueue<Object> objectList = 
      new ConcurrentLinkedQueue<Object>(); 
    // lock must be used to synchronize all the operations on objectList 
    private static final Object lock = new Object(); 

    /** 
    * retrieves the head of the object and prints it 
    */ 
    public static void getHeadObject() { 
     synchronized (lock) { 
      System.out.println(objectList.peek().toString()); 
     } 
    } 

    /** 
    * creates a new object and stores in the list. 
    */ 
    public void addObject() { 
     synchronized (lock) { 
      Object obj = new Object(); 
      objectList.add(obj); 
     } 
    } 
} 
Смежные вопросы