The right way to do equals and hashcode of objects held by AtomicReference

∥☆過路亽.° 提交于 2021-02-04 19:57:20

问题


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 with Objects.equals() and Objects.hash().

In fact, AtomicReference (AR) does work in at least two senses:

  1. AR conforms to the implied specification. The javadocs show that the equals and hashCode methods are not overridden, and that the methods' semantics are therefore defined by Object. Thus, AR objects have "identity" semantics for equals; i.e. two AR objects are "equal" if and only if they are the same object.

  2. AR's behavior is consistent with other mutable types defined by the standard class library: arrays, StringBuilder, other Atomic* 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!