Блокировка синхронизации Java - PullRequest
4 голосов
/ 27 марта 2011

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

В следующем примере listOne.add синхронизируется, означает ли это, что если другой поток обращается к listOne.get, он будет заблокирован, пока первый поток не выйдет из этого блока? Что если второй поток обращается к методам listTwo.get или listTwo.add переменных экземпляра того же объекта, когда первый поток все еще находится в синхронизированном блоке?

List<String> listONe = new ArrayList<String>();
List<String> listTwo = new ArrayList<String>();

/* ... ... ... */

synchronized(this) {
    listOne.add(something);
}

Ответы [ 6 ]

6 голосов
/ 27 марта 2011

Учитывая методы:

  public void a(String s) {
    synchronized(this) {
      listOne.add(s);
    }
  }

  public void b(String s) {
    synchronized(this) {
      listTwo.add(s);
    }
  }

  public void c(String s) {
      listOne.add(s);
  }

  public void d(String s) {
      synchronized(listOne) {
        listOne.add(s);
      }
  }

Вы не можете вызывать a и b одновременно, так как они заблокированы на одной и той же блокировке. Однако вы можете вызывать a и c одновременно (очевидно, с несколькими потоками), так как они не заблокированы на одной и той же блокировке. Это может привести к проблемам с listOne.

Вы также можете вызывать a и d одновременно, поскольку d в этом контексте не отличается от c. Он не использует тот же замок.

Важно, чтобы вы всегда блокировали listOne с одной и той же блокировкой и не разрешали доступ к ней без блокировки. Если listOne и listTwo так или иначе связаны и иногда требуют обновлений одновременно / атомарно, вам потребуется одна блокировка для доступа к ним обоим. В противном случае 2 отдельных замка могут быть лучше.

Конечно, вы, вероятно, будете использовать относительно новые классы java.util.concurrent, если все, что вам нужно, это параллельный список:)

6 голосов
/ 27 марта 2011

Блокировка находится на экземпляре объекта, который вы включаете в синхронизированный блок.

Но будьте осторожны!Этот объект NOT изначально заблокирован для доступа другими потоками.Только те потоки, которые выполняют тот же synchronized(obj), где obj в вашем примере this, но могут быть и в других потоках, также могут быть ссылками на переменные, ждут этой блокировки.Синхронизированные операторы могут обращаться ко всем переменным «заблокированного» объекта, и вы, вероятно, столкнетесь с условиями гонки.

3 голосов
/ 27 марта 2011

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

1 голос
/ 27 марта 2011

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

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

1 голос
/ 27 марта 2011
synchronized(this) {

заблокирует только объект this. Для блокировки и работы с объектом listOne:

synchronized(listOne){
    listOne.add(something);
}

, чтобы к listOne обращались по одному из нескольких потоков.

См .: http://download.oracle.com/javase/tutorial/essential/concurrency/locksync.html

0 голосов
/ 27 марта 2011

Спецификация языка Java определяет значение оператора synchronized следующим образом:

Оператор synchronized получает блокировку взаимного исключения (§17.1) дляот имени исполняющего потока, выполняет блок, затем снимает блокировку.Пока исполняющий поток владеет блокировкой, никакой другой поток не может получить блокировку.

SynchronizedStatement:`
    synchronized ( Expression ) Block`

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

AСинхронизированный оператор выполняется сначала путем вычисления Выражения.

Если вычисление Выражения завершается внезапно по какой-то причине, то синхронизированный оператор завершается преждевременно по той же причине.

В противном случае, если значениевыражение имеет значение null, генерируется исключение NullPointerException.

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

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

То есть, в вашем примере

synchronized(this) {
    listOne.add(something);
}

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

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

void increment() {
    synchronized (this) {
        this.counter = this.counter + 1;
    }
}

void reset() {
    this.counter = 0;
}

некорректно синхронизирован, поскольку второй поток может выполнить reset, пока первый поток прочитал, но еще не записал, counter,вызывая перезапись сброса.

...