Некоторые вопросы по многопоточности Java, - PullRequest
11 голосов
/ 22 июля 2011

У меня есть ряд вопросов, касающихся проблем многопоточности Java.Пожалуйста, предоставьте мне как можно больше помощи.

0) Предположим, у нас есть 2 банковских счета, и нам необходимо перевести деньги между ними в поточно-ориентированном виде.т.е.

accountA.money += transferSum; 
accountB.money -= transferSum; 

Существуют два требования:

  1. никто не должен видеть промежуточные результаты операции (то есть одна сумма счета увеличивается, но другие еще не уменьшаются)
  2. доступ к чтению не должен блокироваться во время операции (т. Е. Старые значения сумм счетов должны отображаться во время операции)

Можете ли вы предложить некоторые идеи по этому поводу?

1) Предположим, что 2 потока изменяют поле некоторого класса с помощью синхронизированного метода или с помощью явной блокировки.Независимо от синхронизации, нет никакой гарантии, что это поле будет видимым для потоков, которые читают его через НЕ синхронизированный метод.- это правильно?

2) Как долго поток, который вызывается методом уведомления, может ожидать блокировки?Предположим, у нас есть такой код:

synchronized(lock) {  
    lock.notifyall();   
    //do some very-very long activity  
    lock.wait() //or the end of synchronized block  
}  

Можем ли мы утверждать, что хотя бы один поток завершится успешно, и захватить блокировку?Может ли сигнал быть потерян из-за тайм-аута?

3) Цитата из Java Concurrency Book:

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

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

4) Все стандартные методы получения и установки являются атомными.Их не нужно синхронизировать, если поле помечено как изменчивое.- это правильно?

5) Инициирование статических полей и статических блоков выполняется одним потоком и, следовательно, не требует синхронизации.- это правильно?

6) Почему поток должен уведомлять других, если он покидает блокировку методом wait (), но не должен это делатьесли он выйдет из блокировки, выйдя из синхронизированного блока?

Ответы [ 4 ]

7 голосов
/ 22 июля 2011

0: Вы не можете.

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

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

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

1) Насколько мне известно, нет никаких гарантий, кроме тех, которые установленыsynchronized или volatile.Если один поток делает синхронизированный доступ, а другой - нет, несинхронизированный доступ не имеет барьера памяти.(если я ошибаюсь, я уверен, что меня исправят или хотя бы понизят)

2) Процитирую JavaDoc: «Пробужденные потоки не смогут продолжаться, пока текущий поток не откажетсязамок на этом объекте. "Если вы решите бросить сон в этот синхронизированный блок, вы будете недовольны.

3) Я должен был бы прочитать эту цитату несколько раз, чтобы быть уверенным, но я считаю, что «однопоточный исполнитель»это ключевая фраза.Если исполнитель работает только в одном потоке, то для всех операций в этом потоке существует строгое отношение «до и после».Это не означает , что другие потоки, работающие в других исполнителях, могут игнорировать синхронизацию.

4) Нет. long и double не являются атомарными (см. JVM).спецификации ).Используйте объект AtomicXXX, если вам нужен несинхронизированный доступ к переменным-членам.

5) Нет. Я не смог найти точную ссылку в спецификации JVM, но в разделе 2.17.5 подразумеваетсячто несколько потоков могут инициализировать классы.

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

1 голос
/ 23 июля 2011

5) Инициирование статических полей и статических блоков выполняется одним потоком и, следовательно, не требует синхронизации. - это правильно?

ВМ выполняет статическую инициализацию в синхронизированном блоке (clazz).

static class Foo {
    static {
        assert Thread.holdsLock(Foo.class); // true

        synchronized(Foo.class){  // redundant, already under the lock
            ....
1 голос
/ 22 июля 2011

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

Если вы не хотите, чтобы промежуточные результаты были видны, вам придется заблокировать обе учетные записи, прежде чем писать.Лучший способ сделать это - убедиться, что вы каждый раз получаете и отпускаете блокировки в одном и том же порядке (в противном случае вы получаете тупик).Например, сначала получите блокировку на меньшем номере счета, а затем на большем.

1) Правильно, весь доступ должен быть через блокировку / синхронизирован или использовать энергозависимый.

2) Навсегда

3) Использование Однопоточного Исполнителя означает, что до тех пор, пока весь доступ осуществляется задачами, выполняемыми этим исполнителем, вам не нужно беспокоиться о безопасности / видимости потоков.

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

5) Нет, два потока могут попробовать инициализировать некоторый статический код, делая наивные реализации Singleton небезопасными.

6) Синхронизация и ожидание / уведомление - два разных, но связанных механизма.Без ожидания / уведомления вам придется вращать блокировку (т.е. продолжать получать блокировку и опрос) для объекта, чтобы получать обновления

0 голосов
/ 22 июля 2011

0) Единственный способ увидеть это - сохранить accountA и accountB в объекте, хранящемся в AtomicReference. Затем вы делаете копию объекта, изменяете его и обновляете ссылку, если она все еще совпадает с исходной ссылкой.

AtomicReference<Accounts> accountRef;

Accounts origRef;
Accounts newRef;
do {
   origRef = accountRef.get();
   // make a deep copy of origRef

   newRef.accountA.money += transferSum; 
   newRef.accountB.money -= transferSum; 
} while(accountRef.compareAndSet(origRef, newRef);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...