Поток Java - проблема синхронизации - PullRequest
2 голосов
/ 03 мая 2010

Из учебника Sun:

Синхронизированные методы обеспечивают простую стратегию предотвращения помех потоков и ошибок согласованности памяти: если объект виден более чем одному потоку, все операции чтения или записи в переменные этого объекта выполняются с помощью синхронизированных методов. (Важное исключение: конечные поля, которые нельзя изменить после создания объекта, могут безопасно считываться с помощью несинхронизированных методов после создания объекта). Эта стратегия эффективна, но может создавать проблемы с живостью, так как мы будем см. далее в этом уроке.

Q1. Означают ли вышеприведенные утверждения, что если объект класса будет совместно использоваться несколькими потоками, то все методы экземпляра этого класса (кроме получателей конечных полей) должны быть синхронизированы, так как методы экземпляра обрабатывают переменные экземпляра?

Ответы [ 10 ]

5 голосов
/ 03 мая 2010

Чтобы понять параллелизм в Java, я рекомендую бесценный Параллелизм Java на практике .

В ответ на ваш конкретный вопрос, хотя синхронизация всех методов является быстрым и грязным способом обеспечения безопасности потоков, она вообще плохо масштабируется. Рассмотрим очень порочный класс Vector. Каждый метод синхронизирован, и он работает ужасно, потому что итерация все еще не безопасна для потоков.

3 голосов
/ 03 мая 2010

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

2 голосов
/ 03 мая 2010

Не обязательно. Вы можете синхронизировать (например, установить блокировку на выделенном объекте) часть метода, например, для доступа к переменным объекта. В других случаях вы можете делегировать задание какому-либо внутреннему объекту (объектам), который уже обрабатывает проблемы синхронизации.
Есть много вариантов, все зависит от алгоритма, который вы реализуете. Хотя «синхронизированные» ключевые слова обычно самые простые.

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

Но, безусловно, есть полезные ресурсы. В частности, на сайте Хайнца Кабуца есть серия интересных статей.
http://www.javaspecialists.eu/archive/Issue152.html (см. полный список на странице)

Если у других людей есть какие-либо ссылки, мне было бы интересно посмотреть также. Я нахожу всю тему довольно запутанной (и, возможно, самой сложной частью ядра Java), особенно с учетом того, что в Java 5 были введены новые механизмы параллелизма

Веселись!

1 голос
/ 04 мая 2010

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

1 голос
/ 03 мая 2010

Фактически, геттеры a тоже должны быть синхронизированы или поля должны быть заполнены volatile. Это потому, что когда вы получаете какое-то значение, вы, вероятно, интересуетесь самой последней версией значения. Видите ли, семантика synchronized блока обеспечивает не только атомарность выполнения (например, он гарантирует, что только один поток выполняет этот блок за один раз), но также и видимость. Это означает, что когда поток входит в блок synchronized, он делает недействительным свой локальный кеш, а когда выходит, он сбрасывает все переменные, которые были изменены, обратно в основную память. volatile переменные имеют одинаковую семантику видимости.

1 голос
/ 03 мая 2010

синхронизировано methodName против синхронизировано (объект)

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

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

т.е. (используя синхронизированное ключевое слово в методе)

class SomeClass {
    private int clickCount  = 0;

    public synchronized void click(){
        clickCount++;
    }
 }

Когда класс определяется таким образом, только один поток за раз может вызывать метод click.

Что произойдет, если этот метод вызывается слишком часто в однопоточном приложении? Вы потратите дополнительное время на проверку того, сможет ли этот поток получить блокировку объекта, когда он не нужен.

class Main {
    public static void main( String  [] args ) {
         SomeClass someObject = new SomeClass();
         for( int i = 0 ; i < Integer.MAX_VALUE ; i++ ) {
             someObject.click();
         }
    }
 }

В этом случае проверка, может ли поток заблокировать объект, будет вызвана без необходимости Integer.MAX_VALUE (2 147 483 647) раз.

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

Итак, как бы вы сделали это в многопоточном приложении?

Вы просто синхронизируете объект:

synchronized ( someObject ) {
    someObject.click();
}

Вектор против ArrayList

Как дополнительное примечание, это использование ( syncrhonized methodName вместо syncrhonized (object) ), кстати, является одной из причин, по которой java.util.Vector теперь заменяется на java.util.ArrayList. Многие из методов Vector синхронизированы.

В большинстве случаев список используется в однопоточном приложении или фрагменте кода (т. Е. Код внутри jsp / servlets выполняется в одном потоке), а дополнительная синхронизация Vector не способствует повышению производительности.

То же самое касается Hashtable, заменяемого на HashMap

1 голос
/ 03 мая 2010

В самом общем виде да.

Неизменяемые объекты не нужно синхронизировать.

Кроме того, вы можете использовать отдельные мониторы / блокировки для переменных переменных экземпляров (или их групп), которые помогут с живостью. А также синхронизировать только те части, где изменяются данные, а не весь метод.

0 голосов
/ 03 мая 2010

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

Все зависит от ситуации, но существует широкий спектр доступных инструментов параллелизма - это лишь некоторые из них.

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

Синхронизация также должна быть глобальной для класса, и простая ошибка, которую можно сделать, - это забыть синхронизировать метод. Когда поток удерживает блокировку для объекта, другие потоки могут по-прежнему обращаться к несинхронизированным методам этого объекта.

0 голосов
/ 03 мая 2010

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

0 голосов
/ 03 мая 2010

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

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

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