2016-12-23 2 views
0

Учитывая, что есть только один замок для каждого экземпляра класса, то почему не Java просто позволяют нам это сделать:Зачем нам нужно указывать блокировку для синхронизированных операторов?

void method() { 
    synchronized { 
     // do something 
    } 

    // do other things 
} 

вместо этого:

void method() { 
    synchronized (lock) { 
     // do something 
    } 

    // do other things 
} 

Что цель указав замок? Разве это имеет значение, если я выбираю один объект как замок над другим? Или я могу выбрать любой случайный объект?

EDIT:

Оказалось, что мое понимание синхронизированных методов является неправильным на фундаментальном уровне.

Я думал, что разные синхронизированные методы или блоки полностью независимы друг от друга независимо от блокировок. Скорее всего, все синхронизированные методы или блоки с одной и той же блокировкой могут быть доступны только одним потоком, даже если такие синхронизированные методы/блоки из разных классов (документация должна была подчеркнуть это больше: ВСЕ синхронизированы методы/блоки, независимо от местоположения , все, что имеет значение, - это замок).

+0

* Имеет ли значение, если я выбираю один объект в качестве замка над другим? * Да, конечно, это имеет значение. Если целью является использование этого в качестве блокировки, используйте синхронизацию (это). Вот что делают регулярные синхронизированные методы: они синхронизируются с этим. –

+2

Имеет ли значение, запираете ли вы свою входную дверь или входную дверь соседей? Конечно. Если вы заблокируете неправильную дверь, люди все равно могут войти в ваш дом. – Andreas

+0

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

ответ

3

Учитывая, что есть только один замок для каждого экземпляра класса, то почему не Java просто позволяют нам это сделать:

void method() { 
    synchronized { 
     // do something 
    } 

    // do other things 
} 

Хотя внутренняя блокировка обеспечивается с каждым экземпляром , это не обязательно «очевидный» замок для использования.

Возможно, вы правы, что могли предоставить synchronized { ... } в качестве сокращенного обозначения для synchronized (this) { ... }. Я не знаю, почему они этого не сделали, но я никогда не пропустил это. Но параллельное программирование сложно, , поэтому сделать объект блокировки явным требуемым параметром может сделать чтение понятным для читателей, что хорошо, как отметил @ajb в комментарии. В любом случае, я не думаю, что синтаксис является вашим основным вопросом, поэтому давайте двигаться дальше.

В чем заключается цель указания замка?

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

Имеет ли значение, если я выбираю один объект в качестве замка над другим?

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

Или я могу выбрать любой случайный объект?

Наверняка нет. См. Предыдущий раздел.

Чтобы понять параллелизм в Java, я рекомендую книгу Java Concurrency in Practice одним из авторов API, или Oracle's tutorials по этому вопросу.

+0

Я в порядке с ними, не предоставляя 'synchronized' как сокращенное выражение для' synchronized (this) ', потому что (1)' synchronized (this) 'не обязательно является наиболее распространенным использованием и (2) имеет объект блокировки быть ясными, делает вещи более ясными для читателя. – ajb

0

В synchronized (lock).., lock может быть блокировка уровня предмета, или это может быть блокировка уровня класса.

  • Example1 Класс замка Уровень:

    private static Object lock=new Object(); 
    synchronized (lock){ 
    //do Something 
    } 
    
  • Example2 Блокировка Уровень объекта:

    private Object lock=new Object(); 
    synchronized (lock){ 
    //do Something 
    } 
    
1

Это так что вы можете заблокировать на что-то совершенно иной, чем this.

Помните, что Vector является "потокобезопасным?" Это не так просто; каждый вызов есть, но код, как это не потому, что она могла быть обновлена ​​между получением размера вектора и получения элемента:

for (int i = 0; i < vector.size(); ++i) System.out.println(vector.get(i)); 

Так как Vector, наряду с Collections.synchronized*, синхронизируется с старше synchronized ключевое слово, вы можете сделать это выше код потокобезопасный заключая все в замке:

synchronized (vector) { 
    for (int i = 0; i < vector.size(); ++i) System.out.println(vector.get(i)); 
} 

Это может быть в способе, который не поточно-, не синхронизированы, или использует ReentrantLock; блокировка вектора отделена от блокировки this.

0

, если у вас есть несколько объектов b1/b2 потребности обновить параллелизм

class A { 
    private B b1, b2; 
} 

, если у вас есть только один замок сказать сам класс А

synchronized (this) { ... } 

тогда предположить, что существует две нити обновления b1 и b2 в то же время, они будут воспроизводиться один за другим, потому что синхронизирован (это)

, но если у вас есть два замка для b1 и b2

private Object lock1 = new Object, lock2 = new Object; 

два потока я уже упомянутые будут играть одновременно, поскольку синхронизированы (Lock1) не влияют на синхронизированный (Lock2) .sometimes означает более высокую производительность.

1

Это, безусловно, имеет значение, какой объект вы используете в качестве замка. Если вы говорите,

void method() { 
    synchronized (x) { 
     // do something 
    } 

    // do other things 
} 

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

void method() { 
    synchronized (this) { 
     // do something 
    }  
    // do other things 
} 

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

Но скажите, что блок кода имеет доступ к общему ресурсу, и вы хотите, чтобы все остальные потоки блокировались от доступа к этому ресурсу. Например, вы получаете доступ к базе данных, и блок выполняет серию обновлений, и вы хотите убедиться, что они выполняются атомарно, то есть никакой другой код не должен обращаться к базе данных, пока вы находитесь между двумя обновлениями. Теперь synchronized (this) не достаточно хорош, потому что вы можете запустить этот метод для двух разных объектов, но получить доступ к одной и той же базе данных. В этом случае вам понадобится блокировка, которая будет одинаковой для всех объектов, которые могут обращаться к одной и той же базе данных. Здесь, делая объект базы данных, блокировка будет работать. Теперь ни один из двух потоков не может использовать method для входа в этот блок одновременно, если они работают с одной и той же базой данных, даже если объекты разные.