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.
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.