简体   繁体   中英

Java concurrency multi read / atom write

i'm trying to implement a cuncurrent read / atomic write in java.

static int atom = 0;

static boolean flag = false;

public static void main(String[] args) {

  new Thread(new Reader()).start();
  new Thread(new Reader()).start();
  new Thread(new Reader()).start();

  Timer timer = new Timer();
  timer.schedule(new TimerTask() {
    @Override
    public void run() {
      write();
    }
  }, 3000);
}

static void write() {
  flag = true;

  Thread.sleep(1000);
  atom++;

  flag = false;
}

static class Reader implements Runnable {

  @Override
  public void run() {
    while (true) {
      Thread.sleep(5);

      if (flag) {
        continue;
      }

      System.out.println(atom);
    }
  }
}

When my threads read the atom var, reads are done until flag is marked true, and continue after value change when flag is turned off.

What is the best way to do this? using sync blocks?

Thanks, Phaedra.

Multi-part answer. In part 1, I'll answer the actual question. In part 2, I'll editorialize a bit.


What you have here is all sorts of racy, including data races. You don't have any happens-before orderings, so the JIT would be perfectly within its rights to turn this:

while(true) {
    ...
    if (flag) {...}
}

Into this:

boolean flagCache = flag;
while(true) {
    if (flagCache) { ... }
}

Note that flagCache isn't ever updated in the second version. The JVM isn't obligated to update it, because flag isn't marked as volatile .

Beyond that, you've got the race condition in write which you seem to have noticed, and a synchronized block could indeed help there. The approach would be to create a separate private static final Object lock = new Object() and then synchronize on that within the write method. That way, writes will synchronize on that object while reads will not. If you go this route, you should also mark atom as volatile . And you definitely don't need or want the Thread.sleep(1000) in that write method, especially not within a synchronized block. That will create a big-time bottleneck if you've got any sort of concurrency on the writes.

(Marking atom as volatile is actually not strictly needed if you do things right [you could piggyback off of the happens-before guarantees that reading from volatile flag gives you], but that's a subtle and tricky maneuver that is best avoided unless you're really trying to optimize the heck out of it, and also consider yourself an advanced programmer in terms of JMM/concurrency.)

Speaking of bottlenecks, your Thread.sleep(5) within your busy wait loop is worth mentioning as an interesting tradeoff. Without it, read threads could spin a lot, wasting CPU. But with it, they could spin too much for no reason. But actually, you don't need the flag at all, or the busy-wait loop. If you just mark atom as volatile and synchronize its writes, then its reads will come through fine. There's a happens-before edge between writing to a volatile field and reading from it.


But I think @Damian Jeżewski is right here. An AtomicInteger does the trick simply, easily and more efficiently (since it uses a compare and set instead of a blocking, potentially context-switching synchronized block).

The general approach to multithreading should almost always be to try and use high-level constructs (ie, things in java.util.concurrent.* ) before using low-level ones (like volatile or even synchronized , and definitely before Object.wait/notify ). That's not a hard and fast rule, of course -- just a general guideline. In this case, look how much thought had to go into a pretty simple requirement -- thread-safe writes with highly concurrent reads -- that AtomicInteger.incrementAndGet gives you for "free".

java.util.concurrent.atomic包中使用AtomicInteger会更好吗?

You need to synchronize on the class, or declare flag and atom as volatile variables, otherwise the concurrent modifications performed by one thread are not guaranteed to be visible on the others.

You may also use a CountDownLatch with a count of 1, instead of waiting until flag is true .

Problems in the code. 1. atom++ introduces the race condition as explained here 2. flag value updated by writer may not become immediately visible to reader 3. atom value updated by writer may not become immediately visible to reader

Here are 2 simple steps required to turn it thread safe.

  1. As pointed out by Damien, make atom AtomicInteger.
  2. Mark flag volatile

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM