Как мог поток 0 печатать счетчик как 1, когда поток 1 уже увеличил его до 2?
В этих двух строках происходит гораздо больше, чем кажется на первый взгляд:
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
Прежде всего, компилятор ничего не знает о потоках. Его работа состоит в том, чтобы выдавать код, который будет иметь тот же конечный результат при выполнении в одном потоке. То есть, когда все сказано и сделано, счетчик должен быть увеличен, и сообщение должно быть напечатано.
Компилятор имеет большую свободу для переупорядочения операций и сохранения значений во временных регистрах вчтобы гарантировать, что правильный конечный результат достигнут наиболее эффективным способом. Так, например, count
в выражении count+" "+...
не обязательно заставит компилятор получить последнее значение глобальной переменной count
. Фактически, он, вероятно, не извлекает из глобальной переменной, потому что знает, что результат операции +
все еще находится в регистре процессора. И, поскольку он не признает, что могут существовать другие потоки, он знает , что значение в регистре не может отличаться от значения, сохраненного в глобальной переменной после выполнения +
.
Во-вторых, самому оборудованию разрешено хранить значения во временных местах и переупорядочивать операции для повышения эффективности, а также допускать, что других потоков нет. Таким образом, даже когда компилятор выдает код, который говорит, что он фактически выбирает или сохраняет глобальную переменную вместо или из регистра, аппаратное обеспечение не обязательно сохраняет или выбирает фактический адрес в памяти.
Предполагая, что вашим примером кода является код Java, все это меняется, когда вы правильно используете блоки synchronized
. Если вы добавите synchronized
к объявлению вашего метода add
, например:
public synchronized void add(int value) {
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
}
Это заставит компилятор признать существование других потоков, и компилятор будет выдавать инструкции, которые заставляют аппаратное обеспечениераспознавать и другие потоки.
Добавляя synchronized
к методу add
, вы заставляете аппаратное обеспечение доставлять фактическое значение глобальной переменной при входе в метод, вы заставляете его фактически записыватьглобальный к моменту возврата метода, и вы предотвращаете одновременное нахождение в методе нескольких потоков.