Pull to refresh

Comments 40

Да ладно вам, к чему снобизм? =)
Автор, спасибо, было интересно и полезно.
Ну я не знаю. И что мне теперь, застрелиться?
Спасибо автору, очень хорошая статья. Про байт-код synchronized-методов не знал, познавательно.
Какая разница-то? Этот байт-код просто перенесется в каждое из мест вызова такого метода — всего-то… Никуда он не денется.
Принципиальной разницы никакой, никто не спорит, просто интересная деталь реализации.
> Принципиальной разницы никакой
Некропост, но:
public synchronized int synchronizedMethodGet() {}
заменяется при компиляции на
public int synchronizedMethodGet() {
synchronized(this){
//
}
}
Листинг 1 показывает вызов и того, и другого.


На самом деле, он показывает вызовы, составляющие тело того и другого. Вызов же любого из них выглядит идентично — invokevirtual.

Печально, что автор забыл сказать, что оба варианта непригодня к использованию — здоровые головой люди стараются не делать синхронизацию по объектам, доступным извне.
автор забыл сказать, что synchronized (this) — это только частный случай его использования и нет смысла сравнивать.
здоровые головой люди стараются не делать синхронизацию по объектам, доступным извне.

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

class Foo {
public void synchronized bar() {
// fast method
}

}


Foo f = new Foo();

T1:

while (true) {
f.bar()
}

T2:

synchronized (f) {
// long operation
}

в таком случае цикл в потоке T1 остановится до тех пор, пока T2 не отдаст блокировку f.
Хотя тут проблема дизайна приложения.
Первый пример плох еще и тем, что может дезориентировать людей, внушив им, что эффективней просто объявить весь метод synchronized, а не ограничить синхронизацию блоком, в котором она действительно необходима.
Рекомендую посмотреть несколько многословный, но добрый репорт относительно использования многопоточности в Java, опубликованный на сайте SEI: Java Concurrency Guidelines (PDF)
Хорошее чтиво, как раз собираюсь написать статью на тему concurrency в Java
Создание synchronized-блока выдало 16 строк байт-кода, тогда как synchronized-метода – только 5.

Ок, и что из этого следует?
… что synchronized-метод может быть более быстрым из двух непригодных к использованию вариантов кода. :) Но если учесть JIT, то я бы не взялся предсказывать, будет ли разница и в чью пользу.
Не будет он более быстрым. Для synchronized метода происходит все то же самое, но в самой виртуальной машине при осуществлении вызова.
так то ж в самой виртуальной машине на Си или Си++
а то на яве
ежу понятно, что на ява будет медленнее
>>ежу понятно, что на ява будет медленнее
Если вы тот самый ёж, то объясните почему будет медленнее?
Не в виртуальной машине, а в месте вызова. Никакой магии нет.
Правда? А покажете в байткоде, где это место?

public static void normal();
  Code:
   0:   getstatic       #2; //Field test:I
   3:   iconst_1
   4:   iadd
   5:   putstatic       #2; //Field test:I
   8:   return

public static synchronized void blocking();
  Code:
   0:   getstatic       #2; //Field test:I
   3:   iconst_1
   4:   iadd
   5:   putstatic       #2; //Field test:I
   8:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #3; //Method normal:()V
   3:   invokestatic    #4; //Method blocking:()V
   6:   return
В виртуальной машине. Точнее даже, в ассемблере, который генерирует JIT.
> Основанная на отражении утилита
Стоит ли переводить reflection? По-моему, на английском понятнее :-)

А вообще познавательно, спасибо.
еще для тех, кто хочет расширить свои познания в многопоточном программировании на джаве рекомендовано прочитать Java Concurrency in Practice
Здесь это явно ошибка.
set просто изменит значение на новое, а compareAndSet проверит сначала, что значение с момента, когда вы его прочитали, не изменилось и только после этого установит новое.
Скорее всего там забыли вставить цикл:
     public void setWhatImReading( Book whatImReading )
    {
         //this.whatImReading = whatImReading;        
         for(;;){
             if(updater.compareAndSet( this, this.whatImReading, whatImReading)) return;
         }
    }

, либо ошиблись с методом.
Сейчас попрошу автора исправить или уточнить
Ну из первого листинга ничего не следует. Как минимум в плане производительности. И JIT все уровняет, и реально в обоих случаях придется выполнять какие-то атомарные операции.
Комментарии и дополнения:

1. Несмотря на то, что synchronized block выглядит длиннее, в действительности он работает точно так же, как и synchronized method. Во-первых, строки 11-15 не исполняются, они нужны лишь для обработки IllegalMonitorStateException. Во-вторых, monitorenter и monitorexit и сопутствующие операции выполняются в обоих случаях, просто в случае synchronized метода это происходит неявно на уровне виртуальной машины при вызове метода.

3. Начиная с Java 5 ключевое слово volatile имеет еще одно очень важное значение. Доступ к volatile полю окружен data memory barrier'ом, что гарантирует упорядоченность операций чтения-записи памяти относительно доступа к этому полю. Иначе компилятор или сам процессор вправе переупорядочивать инструкции (out-of-order execution).

4. В качестве примера неатомарного доступа к не-volatile полю можно рассмотреть чтение/запись поля типа long на 32х битной архитектуре. Оно реализуется в виде двух операций чтения/записи двух «половинок» поля. Таким образом, если один thread пишет longField = 0x1111111122222222L; longField = 0x3333333344444444L; то другой thread в какой-то момент может теоретически увидеть значение 0x1111111144444444L. Запись volatile поля происходит чуть хитрее, и такой проблемы быть не может.

5. Атомарные примитивы — хороший способ сделать поле thread-safe без дорогой блокировки, т.к. большинство архитектур аппаратно поддерживают атомарные операции типа compare-and-swap.
> Начиная с Java 5 ключевое слово volatile имеет еще одно очень важное значение.

Я читал, что с введением этого свойства volatile по производительности стали практически как обычные блокировки. Вы не подскажете, так ли это?
Зависит от архитектуры процессора и сценария использования. На x86 volatile load такой же быстрый, как и обычный load (за исключением volatile long на 32-битной системе, для которого используются FPU инструкции). На однопроцессорной машине тоже все в порядке. Плохо на многопроцессорной машине с volatile store, который сопровождается инструкцией lock add [esp], 0, что выливается во временную блокировку шины данных и инвалидацию кэша процессора.

Синхронизация с помощью мониторов (synchronized блоки) тоже бывает разная. Она почти ничего не стоит до тех пор, пока монитор используется лишь одним тредом (за счет BiasedLocking). Как только второй тред попробует синхронизоваться на том же мониторе, BiasedLocking перестает работать, и задействуются опять же атомарные инструкции с lock префиксом.
Если интересно разобраться в многопоточности в Яве, я бы советовал начать с этой ссылки www.rsdn.ru/forum/java/3622844.flat.aspx и постепенно снижать количество вопросов, на которые нечего ответить. Все тривиально гуглится. Ну и книжку по многопоточности явы 5+ обязательно прочитайте.
Поскольку тезис N1 был воспринят неоднозначно, разрушители легенд решили проверить миф о том, есть ли все-таки разница между работой synchronizedMethodGet и synchronizedBlockGet :)

Для проверки я запустил программку из Листинга 1 с такими параметрами:
java -Xcomp -XX:CompileOnly=SynchronizationExample.synchronizedMethodGet -XX:CompileOnly=SynchronizationExample.synchronizedBlockGet -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly SynchronizationExample

Анализ показал, что машинный код, сгенерированный C1-компилятором для методов synchronizedMethodGet и synchronizedBlockGet оказался одинаковым с точностью до байта! Таким образом, миф разрушен (или подтвержден, кто как предполагал :) Пруф.
Опечатка в 3 листинге. При объявлении «update» упущен знак равенства.
Но спасибо за перевод. Very useful.
Я думаю, что POJO не стоит переводить. А то каждый раз попытка перевести это понятие превращается в какую-то непонятную тафталогию :) Вот вам и пример: ru.wikipedia.org/wiki/POJO
Кстати то же самое можно применить EJB. Когда мы имеем ввиду EJB, мы говорим EJB и всем понятно что это. Не обязательно наворачивать «Энтерпрайзовые Java-бобы» ;)
А так в избранное. Рекомендую Java Concurrency in Practice.
Какой контраст! Пафосный заголовок и ложь незнания внутри. Я про пример из «Volatile против synchronized», где описывается несуществующая для 32-битного типа проблема.
Only those users with full accounts are able to leave comments. Log in, please.