Подтвердить что ты не робот

Почему два AtomicIntegers никогда не равны?

Я наткнулся на источник AtomicInteger и понял, что

new AtomicInteger(0).equals(new AtomicInteger(0))

равно false.

Почему это? Это какой-то "защитный" выбор дизайна, связанный с проблемами concurrency? Если да, то что может пойти не так, если бы оно было реализовано по-другому?

(Я понимаю, что вместо get и == я мог бы использовать.)

4b9b3361

Ответ 1

Отчасти это связано с тем, что AtomicInteger не является заменой общего назначения для Integer.

В пакете java.util.concurrent.atomic указано:

Атомные классы не являются заменами общего назначения для java.lang.Integer и связанные классы. Они не определяют методы таких как hashCode и compareTo. (Поскольку атомные переменные ожидается, что они будут мутированы, они плохо подходят для клавиш хеш-таблицы.)

hashCode не реализован, и так происходит с equals. Частично это объясняется гораздо большим обоснованием, которое обсуждается в архивах списков рассылки, о том, следует ли AtomicInteger расширять Number или нет.

Одна из причин, почему класс AtomicXXX не является заменой примитива и не реализует интерфейс Comparable, заключается в том, что в большинстве сценариев нет смысла сравнивать два экземпляра класса AtomicXXX, Если два потока могут получить доступ и изменить значение AtomicInteger, то результат сравнения недействителен, прежде чем использовать результат, если поток изменяет значение AtomicInteger. Такое же обоснование хорошо подходит для метода equals - результат для теста равенства (который зависит от значения AtomicInteger) действителен только до того, как поток мутирует один из рассматриваемых AtomicInteger.

Ответ 2

На первый взгляд, это похоже на простое упущение, но, может быть, действительно имеет смысл фактически просто использовать idenity, равный Object.equals

Например:

AtomicInteger a = new AtomicInteger(0)
AtomicInteger b = new AtomicInteger(0)

assert a.equals(b)

кажется разумным, но b на самом деле не является a, он предназначен как изменяемый держатель для значения и поэтому не может реально заменить a в программе.

и

assert a.equals(b)
assert a.hashCode() == b.hashCode()

должен работать, но что, если значение b изменяется между ними.

Если это повод, то это было не стыдно, что он не был зарегистрирован в источнике для AtomicInteger.

В стороне: хорошая функция также могла бы позволить AtomicInteger быть равно Integer.

AtomicInteger a = new AtomicInteger(25);

if( a.equals(25) ){
    // woot
}

это означало бы, что для того, чтобы быть рефлексивным, в этом случае Integer должен был бы принимать AtomicInteger в нем тоже.

Ответ 3

Я бы сказал, что поскольку точка AtomicInteger заключается в том, что операции могут выполняться атомарно, было бы трудно обеспечить, чтобы эти два значения сравнивались атомарно, и поскольку AtomicIntegers обычно являются счетчиками, вы бы получили некоторые нечетное поведение.

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

Ответ 4

Я подозреваю, что сравнение значений - это не-go, потому что нет способа сделать это атомарно переносимым способом (без блокировок, то есть).

И если нет атомарности, тогда переменные могли бы сравниться одинаково, даже если они никогда не содержали одно и то же значение одновременно (например, если a изменено с 0 до 1 точно в то же время, что и b изменено от 1 до 0).

Ответ 6

Представьте, что если equals был переопределен, и вы поместили его в HashMap, а затем измените значение. Плохие вещи произойдут:)

Ответ 7

equals правильно реализована: экземпляр AtomicInteger может только соответствовать самому себе, поскольку только тот же самый экземпляр будет, по-видимому, хранить одну и ту же последовательность значений с течением времени.

Напомним, что классы Atomic* действуют как ссылочные типы (например, java.lang.ref.*), предназначенные для обертывания фактического "полезного" значения. В отличие от этого в функциональных языках (см., Например, Clojure Atom или Haskell IORef), различие между ссылками и значениями довольно размыто в Java (видоизменяемость), но оно все еще существует.

Учитывая, что текущее завернутое значение класса Atomic в качестве критерия равенства является вполне понятным заблуждением, поскольку это подразумевает, что new AtomicInteger(1).equals(1).

Ответ 8

Одно ограничение с Java заключается в том, что нет возможности различать экземпляр изменяемого класса, который может и будет мутирован, из экземпляра mutable-class, который никогда не будет подвергаться действиям, которые могут его изменить (*). Ссылки на вещи первого типа следует считать равными, если они относятся к одному и тому же объекту, тогда как ссылки на вещи последнего типа часто считаются равными, если они относятся к объектам с эквивалентным состоянием. Поскольку Java допускает только одно переопределение виртуального метода equals(object), разработчики изменяемых классов должны угадать, будут ли достаточные экземпляры соответствовать последнему шаблону (т.е. Быть сохранены таким образом, что они никогда не будут мутированы), чтобы оправдать наличие equals() и hashCode() ведут себя как способ, подходящий для такого использования.

В случае с чем-то вроде Date существует много классов, которые инкапсулируют ссылку на Date, которая никогда не будет изменена и которая хочет иметь собственное отношение эквивалентности, включает в себя эквивалентность значений инкапсулированного Date. По существу, для Date имеет смысл переопределить equals и hashCode для проверки эквивалентности значений. С другой стороны, сохранение ссылки на AtomicInteger, которое никогда не будет изменено, было бы глупо, поскольку вся цель этого типа сосредотачивается вокруг изменчивости. Экземпляр AtomicInteger, который никогда не будет мутированным, может, во всех практических целях, просто быть Integer.

(*) Любое требование, которое конкретный экземпляр никогда не мутирует, является только обязательным, если либо (1) информация о его хэш-значении идентичности существует где-то, либо (2) более чем одна ссылка на объект существует где-то во Вселенной. Если ни одно условие не относится к экземпляру, указанному в Foo, замена Foo ссылкой на клон Foo не будет иметь наблюдаемого эффекта. Следовательно, можно было бы мутировать экземпляр без нарушения требования о том, что он "никогда не мутирует", притворяясь заменой Foo клоном и мутацией "клона".

Ответ 9

equals используется не только для равенства, но и для удовлетворения его контракта с hashCode, т.е. в хэш-наборах. Единственный безопасный подход для хеш-коллекций - это то, что изменяемый объект не зависит от их содержимого. то есть для измененных ключей HashMap совпадает с использованием IdentityMap. Таким образом, хэш-код и равны ли два объекта, не изменяется при изменении содержимого клавиш.

Итак, new StringBuilder().equals(new StringBuilder()) также неверно.

Чтобы сравнить содержимое двух AtomicInteger, вам нужно ai.get() == ai2.get() или ai.intValue() == ai2.intValue()

Предположим, что у вас есть изменяемый ключ, где hashCode и равно меняются в зависимости от содержимого.

static class BadKey {
    int num;
    @Override
    public int hashCode() {
        return num;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof BadKey && num == ((BadKey) obj).num;
    }

    @Override
    public String toString() {
        return "Bad Key "+num;
    }
}

public static void main(String... args) {
    Map<BadKey, Integer> map = new LinkedHashMap<BadKey, Integer>();
    for(int i=0;i<10;i++) {
        BadKey bk1 = new BadKey();
        bk1.num = i;
        map.put(bk1, i);
        bk1.num = 0;
    }
    System.out.println(map);
}

печатает

{Bad Key 0=0, Bad Key 0=1, Bad Key 0=2, Bad Key 0=3, Bad Key 0=4, Bad Key 0=5, Bad Key 0=6, Bad Key 0=7, Bad Key 0=8, Bad Key 0=9}

Как вы можете видеть, теперь у нас есть 10 ключей, все одинаковые и с одинаковым хэш-кодом!