Я читал этот пост.
И автор говорил о взломе hashCode()
в String
в многопоточной среде.
Имея:
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
Изменился на:
public int hashCode() {
if (hash == 0) {
int off = offset;
char val[] = value;
int len = count;
int h = 0;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return hash;
}
Который автор говорит, и я цитирую:
"То, что я сделал здесь, это добавление дополнительного чтения: второе чтение хэша перед возвратом. Как бы странно это ни звучало и как бы маловероятно это ни было, первое чтение может вернуть правильно вычисленное значение хеш-функции, и второе чтение может вернуть 0! Это разрешено в модели памяти, потому что модель допускает обширное переупорядочение операций. Второе чтение фактически может быть перемещено в вашем коде, так что ваш процессор сделает это раньше первого! "
Далее, просматривая комментарии, кто-то говорит, что его можно переупорядочить
int h = hash;
if (hash == 0) {
...
}
return h;
Как это возможно? Я думал, что переупорядочение включает только перемещение программных операторов вверх и вниз. Каким правилам он следует? Я погуглил, прочитал FAQ по JSR133, проверил книгу "Параллелизм Java на практике", но мне не удается найти место, которое поможет мне понять, в частности, при переупорядочении. Если кто-то может указать мне правильное направление, я был бы очень признателен.
Благодаря Луи, разъясняющему значение слова "Переупорядочение", я не думал в терминах "byteCode"
Тем не менее, я до сих пор не понимаю, почему разрешено перемещать 2-е чтение вперед, это моя наивная попытка перевести его в несколько "байт-код" формат.
Для упрощения операции, используемые для вычисления calchash()
выражаются как calchash()
. Поэтому я выражаю программу как:
if (hash == 0) {
h = calchash();
hash = h;
}
return hash;
И моя попытка выразить это в форме "байт-кода":
R1,R2,R3 are in the operands stack, or the registers
h is in the array of local variables
В порядке программы:
if (hash == 0) { ---------- R1 = read hash from memory (1st read)
---------- Compare (R1 == 0)
h = calchash(); ---------- R2 = calchash()
---------- h = R2 (Storing the R2 to local variable h)
hash = h; ---------- Hash = h (write to hash)
}
return hash ---------- R3 = read hash from memory again(2nd read)
---------- return R3
Изменение порядка преобразования (Моя версия основана на комментариях):
---------- R3 = read hash from memory (2nd read) *moved*
if (hash == 0) { ---------- R1 = read hash from memory (1st read)
---------- Compare (R1 == 0)
h = calchash(); ---------- R2 = calchash()
---------- h = R2 (Storing the R2 to local variable h)
hash = h; ---------- hash = h (write to hash)
}
return hash ---------- return R3
Проверяя комментарии еще раз, я нашел ответ автора:
Изменение порядка трансформации (из блога)
r1 = hash;
if (hash == 0) {
r1 = hash = // calculate hash
}
return r1;
Этот случай фактически работает в одном потоке, но возможен сбой в нескольких потоках.
Кажется, что JVM делают упрощения на основе
h = hash and it simplifies the use of R1, R2, R3 to single R1
Таким образом, JVM делает больше, чем просто переупорядочивание инструкций, и это также уменьшает количество используемых регистров.