Некоторые вопросы о многопоточности java,

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

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

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

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

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

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

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

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

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

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

3) Цитата из Java Concurrency Книга:

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

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

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

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

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

Ответ 1

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

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

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

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

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

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

3) Мне нужно было бы прочитать эту цитату несколько раз, чтобы убедиться, но я считаю, что "однопоточный исполнитель" - это ключевая фраза. Если исполнитель работает только с одним потоком, то для всех операций над этим потоком существует строгая связь "до". Он не означает означает, что другие потоки, работающие в других исполнителей, могут игнорировать синхронизацию.

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

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

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

Ответ 2

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

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

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

2) Навсегда

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

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

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

6) Sync и Wait/Notify - это два разных, но связанных с ними механизма. Не ждите/не сообщите, что вам нужно будет блокировать блокировку (т.е. Получать блокировку и опрос) на объекте для получения обновлений

Ответ 3

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

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

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

        synchronized(Foo.class){  // redundant, already under the lock
            ....

Ответ 4

0) Единственный способ, с помощью которого я могу это сделать, - сохранить учетную запись A и учетную запись B в объекте, хранящемся в 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);