Во-первых: не теряйте TargetInvocationException - это - ценная информация, когда Вы захотите отладить вещи.
Второй: Оберните TIE как InnerException в Вашем собственном типе исключительной ситуации и поместите свойство OriginalException, которое связывается с тем, что Вы нуждаетесь (и сохраняете весь стек вызовов в целости).
Треть: Позвольте пузырю TIE из своего метода.
Короткий ответ заключается в том, что JSR-133 заходит слишком далеко в своем объяснении . Это не является серьезной проблемой, потому что JSR-133 - ненормативный документ, который не является частью языка или стандартов JVM. Скорее, это всего лишь документ, объясняющий одну возможную стратегию, которая достаточна для реализации модели памяти, но в целом не является необходимой . Вдобавок ко всему, комментарий о «очистке кеша» в основном неуместен, поскольку практически нулевые архитектуры реализуют модель памяти Java, выполняя любой тип «
Таким образом, цитируемый вами текст формально не описывает то, что гарантирует Java, а скорее описывает, как некая гипотетическая архитектура с очень слабым упорядочением памяти и гарантиями видимости могла удовлетворить требования модели памяти Java с использованием очистки кеша. . Любое фактическое обсуждение очистки кеша, основной памяти и т. Д. Явно неприменимо к Java, поскольку эти концепции не существуют в спецификации абстрактного языка и модели памяти.
На практике гарантии, предлагаемые моделью памяти, весьма значительны. слабее, чем полная очистка - сброс всего кеша с помощью каждой атомарной операции, связанной с параллелизмом или блокировкой будет непомерно дорогостоящим - а на практике это почти никогда не делается. Вместо этого используются специальные атомарные операции ЦП, иногда в сочетании с инструкциями барьера памяти , которые помогают обеспечить видимость и упорядоченность памяти. Таким образом, очевидное несоответствие между дешевой неконтролируемой синхронизацией и «полной очисткой кеша» разрешается за счет того, что первое верно, а второе - нет - модель памяти Java не требует полного сброса (и на практике сброса не происходит).
Если формальная модель памяти слишком сложна для переваривания (вы не будете одиноки), вы также можете глубже погрузиться в эту тему, взглянув на Поваренную книгу Дуга Ли , которая находится в Фактически, ссылка на него содержится в FAQ по JSR-133, но решает проблему с точки зрения конкретного оборудования, поскольку он предназначен для разработчиков компиляторов. Там они говорят о том, какие именно барьеры нужны для конкретных операций, включая синхронизацию - и обсуждаемые там барьеры довольно легко сопоставить с реальным оборудованием. Большая часть фактического отображения обсуждается прямо в кулинарной книге.
Обновлениям x требуется синхронизация, но делает ли приобретение замка очистить значение y также от кеш? Я не могу представить, что это будет случае, потому что если бы это было правдой, такие методы, как разделение замков, могут не поможет.
Я не уверен, но думаю, что ответ может быть «да». Учтите следующее:
class Foo {
int x = 1;
int y = 1;
..
void bar() {
synchronized (aLock) {
x = x + 1;
}
y = y + 1;
}
}
Теперь этот код небезопасен, в зависимости от того, что происходит в остальной части программы. Однако я думаю, что модель памяти означает, что значение y
, видимое полосой
, не должно быть старше «реального» значения на момент получения блокировки. Это означало бы, что кеш должен быть признан недействительным для y
, а также для x
.
Также JVM может надежно анализировать код, чтобы гарантировать, что y не изменяется в другом синхронизированном блоке, используя та же самая блокировка?
Если блокировка эта
, этот анализ выглядит так, как будто он будет возможен в качестве глобальной оптимизации после того, как все классы будут предварительно загружены. (Я не говорю, что это было бы легко или целесообразно ...)
В более общих случаях проблема доказательства того, что данная блокировка когда-либо используется только в связи с данным экземпляром «владеющим», вероятно, является неразрешимой. .
мы Java-разработчики, мы знаем только виртуальные машины, а не настоящие!
позвольте мне теоретизировать, что происходит, но я должен сказать, что не знаю, о чем говорю.
скажем, поток A запущен на CPU A с кешем A, поток B работает на CPU B с кешем B,
поток A читает y; CPU A извлекает y из основной памяти и сохраняет значение в кэше A.
поток B присваивает новое значение «y». На этом этапе ВМ не нужно обновлять основную память; что касается потока B, он может читать / писать на локальном образе 'y'; возможно, «y» - не что иное, как регистр процессора.
поток B выходит из блока синхронизации и освобождает монитор. (когда и где он вошел в блок, значения не имеет). к этому моменту поток B обновил довольно много переменных, включая «y». Все эти обновления теперь должны быть записаны в основную память.
ЦП B записывает новое значение y, чтобы поместить «y» в основную память. (Я полагаю, что) почти МГНОВЕННО информация «main y обновлена» подключается к кешу A, а кеш A делает недействительной собственную копию y. Это должно было произойти действительно БЫСТРО на оборудовании.
поток A получает монитор и входит в блок синхронизации - в этот момент ему не нужно ничего делать с кешем A. 'y' уже ушел из кеша A. когда поток A снова читает y, он только что из основной памяти с новым значением, присвоенным B.
рассмотрим другую переменную z, которая также была кэширована A на шаге (1), но она не обновлена потоком B на шаге (2 ). он может сохраняться в кэше A до шага (5). доступ к 'z' не замедляется из-за синхронизации.
Если приведенные выше утверждения имеют смысл, тогда цена действительно не очень высока.
дополнение к шагу (5): поток A может иметь свой собственный кеш, который даже быстрее, чем кеш A - например, он может использовать регистр для переменной 'y'. это не будет аннулировано на шаге (4), поэтому на шаге (5) поток A должен стереть свой собственный кеш при входе в синхронизацию. хотя это не слишком большой штраф.
Поскольку y выходит за рамки синхронизированного метода, нет никаких гарантий, что его изменения будут видны в других потоках. Если вы хотите гарантировать, что изменения y будут одинаковыми для всех потоков, тогда все потоки должны использовать синхронизацию при чтении / записи y.
Если одни потоки изменяют y синхронно, а другие нет, то вы получите неожиданное поведение. Все изменяемые состояния, совместно используемые потоками, должны быть синхронизированы, чтобы были какие-либо гарантии при обнаружении изменений между потоками. Весь доступ всех потоков к общему изменяемому состоянию (переменным) должен быть синхронизирован.
И да, JVM гарантирует, что пока блокировка удерживается, никакой другой поток не может войти в область кода, защищенную той же блокировкой.
synchronize гарантирует, что только один поток может ввести блок кода. Но это не гарантирует, что изменения переменных, выполненные в синхронизированном разделе, будут видны другим потокам. Только потоки, входящие в синхронизированный блок, гарантированно увидят изменения. Эффекты синхронизации памяти в Java можно сравнить с проблемой блокировки с двойной проверкой применительно к C ++ и Java. Блокировка с двойной проверкой широко цитируется и используется как эффективный метод реализации отложенной инициализации в многопоточной среде. К сожалению, он не будет надежно работать независимо от платформы при реализации на Java без дополнительной синхронизации. При реализации на других языках, таких как C ++, это зависит от модели памяти процессора, переупорядочения, выполняемого компилятором, и взаимодействия между компилятором и библиотекой синхронизации. Поскольку ни один из них не определен в таком языке, как C ++, мало что можно сказать о ситуациях, в которых он будет работать. Чтобы заставить его работать в C ++, можно использовать явные барьеры памяти, но эти барьеры недоступны в Java.