问题
AtomicReference
doesn't work with Objects.equals()
and Objects.hash()
.
AtomicReference<String> ref1 = new AtomicReference<>("hi");
AtomicReference<String> ref2 = new AtomicReference<>("hi");
System.out.println(Objects.equals(ref1, ref2)); //false
System.out.println(Objects.equals(ref1.get(), ref2.get())); //true but potentially not thread safe
System.out.println(Objects.hash(ref1) == Objects.hash(ref2)); //false
System.out.println(Objects.hash(ref1.get()) == Objects.hash(ref2.get())); //true but potentially not thread safe
What's the best way to check if two objects held by AtomicReference
are equal and calculate the same hash in a way that both works and is thread safe?
回答1:
What's the best way to check if two [variables]...are equal...in a way that both works and is thread safe?
That question doesn't make any sense. Let's say you have thread A which wants to test the variables, and thread B which could change either one of them at any time without warning.
You want to find out if the variables are equal? OK, then what? If your "thread safe" equality test says they're equal, What stops thread B from changing either one of them in between the time when you performed the equality test and the time when you did something with the information.
There's no value in knowing whether two variables are equal at some instant in time: You want to know if the variables are equal while the program is in some particular state, and then you want to do something about it before the state can change.
So, yeah, you need to be synchronized
on a lock object, but it can't only be to protect the equality test. It has to prevent the program from changing the state that you care about during the entire time that you test the variables and then do something based on the result of the test.
回答2:
First of all, I would like to take issue with this statement:
AtomicReference
doesn't work withObjects.equals()
andObjects.hash()
.
In fact, AtomicReference
(AR) does work in at least two senses:
AR conforms to the implied specification. The javadocs show that the
equals
andhashCode
methods are not overridden, and that the methods' semantics are therefore defined byObject
. Thus, AR objects have "identity" semantics forequals
; i.e. two AR objects are "equal" if and only if they are the same object.AR's behavior is consistent with other mutable types defined by the standard class library: arrays,
StringBuilder
, otherAtomic*
types and so on.
I will grant you that AR's behavior may not be helpful to what you are trying to do. But you would find that Objects.equals()
and Objects.hash()
have the same problems with the other mutable types that I mentioned.
The correct way to compare the wrapped values of a pair of atomic references is:
Objects.equals(ref1.get(), ref2.get());
You state (correctly) that this is not thread-safe, but this is (to my mind) expected for atomic types. As a general rule, a sequence of two atomic operations is non-atomic. As a general rule, to perform two operations as an atomic whole, you need to do some form of locking.
So a simple (but flawed) approach is to do this:
synchronized (ar1) {
synchronized (ar2) {
result = ar1.get() == ar2.get();
}
}
The flaw is that there is a risk of deadlock ... if two threads execute the above code simultaneously with the ar1
and ar2
interchanged.
A (hypothetical) fix for the flaw is to ensure that the threads lock ar1
and ar2
in the same order. You can (almost always) achieve that by using ar1.hashCode()
and ar2.hashCode()
to give you a consistent order; e.g.
int h1 = ar1.hashCode();
int h2 = ar2.hashCode();
if (h1 < h2) {
synchronized (ar1) {
synchronized (ar2) {
result = ar1.get() == ar2.get();
}
} else if (h1 > h2) {
synchronized (ar2) {
synchronized (ar1) {
result = ar1.get() == ar2.get();
}
} else if (ar1 == ar2) {
result = true;
} else {
// h1 == h2, but ar1 != ar2
}
But as you can see, in a tiny percentage of cases, hashCode ordering won't work.
And I don't know if there is a solution for that subcase ... using mutex locking.
The other approach to this is to point out that it is actually unreasonable to expect that you can do this. Here's the thing. Even if you were able to test that two AtomicReference
objects did refer to the same object, that result is only valid for a specific instant in time. The instant after the test completes, the result could be invalidated by some thread changing one of the ARs.
The bottom line is that the test is not useful in that it doesn't tell you anything that your application can rely on. So (IMO) there is little point in implementing it, and even less point in worrying about race conditions.
The practical alternative is to use external locking on Lock
or mutex objects that don't have the problem of potential deadlocks.
回答3:
You want to block both references for manipulation during equals
and thus make equals
threadsafe.
I think you can't do this just with AtomicReference
since it is not responsible for external synchronization. You have to do this manually by controlling the accessing threads.
来源:https://stackoverflow.com/questions/53884445/the-right-way-to-do-equals-and-hashcode-of-objects-held-by-atomicreference