user2763361
user2763361

Reputation: 3919

How to ensure that the state of an enum is specific to one instance

I'm having an issue that cropped up in multithreading. I have an enum with state set through setters instead of the constructor. I'll have 2 threads that make instances of this enum for different uses. Each thread will have its own use for this setter. Each thread will have multiple instances of these enums floating around.

My problem is that when I call the setter in one thread and it's immediately called in another thread, the state in the initial enum is overwritten by what was provided to the setter in the second enum.

I have provided a MWE below. How do I fix this issue?

This isn't just a problem in a multi-threaded context, but I would like the solution to be thread-safe as well.

Note also that I use reference based testing, e.g. if(enumValue == TestEnumMutate.A) must return true if enumValue is indeed A.

enum TestEnumMutate {
  A(0),
  B(0);

  int i;

  TestEnumMutate(int i) {
    this.i=i;
  }

  public void setI(int i) {
    this.i=i;
  }

  public void print() {
    System.out.println(i);
  }
}

class TestClassMutate {
  private int i = 0;

  public TestClassMutate() { }

  public void setI(int i) {
    this.i=i;
  }

  public void print() {
    System.out.println(i);
  }
}

public class ThreadSafeTest implements Runnable{
  private boolean sleep;
  private boolean isTestingEnum;

  public ThreadSafeTest(boolean sleep,boolean isTestingEnum) {
    this.sleep=sleep;
    this.isTestingEnum=isTestingEnum;
  }

  public void run() {
    if(isTestingEnum) {
      this.runEnumTest(sleep);
    } else {
      this.runClassTest(sleep);
    }
  }

  private void runClassTest(boolean sleep) {
    TestClassMutate foo = new TestClassMutate();

    if(sleep) {
      try {
        Thread.sleep(500);
      } catch(Exception e) {};
    } else {
      foo.setI(1);
    }

    foo.print();
  }

  private void runEnumTest(boolean sleep) {
    final TestEnumMutate foo = TestEnumMutate.A;

    if(sleep) {
      try {
        Thread.sleep(500);
      } catch(Exception e) {};
    } else {
      foo.setI(1);
    }
    foo.print();
  }

  public static void main(String[] args) throws Exception {
    System.out.println("Should print 1 then 0 if one thread's instance didn't"+
      " interfere with the other thread's instance");
    Thread enumTestThread1 = new Thread(new ThreadSafeTest(true,true));
    Thread enumTestThread2 = new Thread(new ThreadSafeTest(false,true));

    enumTestThread1.start();
    enumTestThread2.start();

    enumTestThread1.join();
    enumTestThread2.join();

    System.out.println("Should print 1 then 0 if one thread's instance didn't"+
      " interfere with the other thread's instance");
    Thread classTestThread1 = new Thread(new ThreadSafeTest(true,false));
    Thread classTestThread2 = new Thread(new ThreadSafeTest(false,false));

    classTestThread1.start();
    classTestThread2.start();

    classTestThread1.join();
    classTestThread2.join();
  }
}

Upvotes: 0

Views: 559

Answers (2)

dimo414
dimo414

Reputation: 48874

Note that you're effectively describing conflicting requirements - an enum provides type-safe singletons; you cannot have multiple instances of the same enum value. Are you perhaps looking for the behavior EnumMap provides, the ability to map enums to different values? You could then have each thread or class hold its own mapping. This way the enums stay constant but the values based on the enum can change at will.

An alternative would be to use a ThreadLocal, this lets you specify a unique value per-thread (under the covers, it's essentially a map, like above), so that multiple threads don't interrupt each other.

While an AtomicInteger, as Unihedron suggests, would ensure thread-safe incrementing, it's not clear to me that is what you're actually trying to do; if I understand correctly you don't want the behavior of one thread to impact the behavior of another, which means you simply shouldn't be storing this state in your enums at all.

Upvotes: 3

Unihedron
Unihedron

Reputation: 11051

You may look into the use of an AtomicInteger instead of using an enum or a mutable object to wrap a value:

class MyWrapper {
    AtomicInteger aInt = new AtomicInteger(0);
}

within threads, you can use the following, thread-safely:

{
    MyWrapper.aInt.set(12);
    MyWrapper.aInt.addAndGet(1);
    // ...
}

As all AtomicInteger objects are mutable, this does what you will need. Also, enums are immutable, so they will not perform what you need.

Upvotes: 1

Related Questions