Nested spin-lock vs volatile check - java

I was about to write something about this, but maybe it is better to have a second opinion before appearing like a fool...
So the idea in the next piece of code (android's room package v2.4.1, RoomTrackingLiveData), is that the winner thread is kept alive, and is forced to check for contention that may have entered the process (coming from losing threads) while computing.
While fail CAS operations performed by these losing threads keep them out from entering and executing code, preventing repeating signals (mComputeFunction.call() OR postValue()).
final Runnable mRefreshRunnable = new Runnable() {
#WorkerThread
#Override
public void run() {
if (mRegisteredObserver.compareAndSet(false, true)) {
mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
}
boolean computed;
do {
computed = false;
if (mComputing.compareAndSet(false, true)) {
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
try {
value = mComputeFunction.call();
} catch (Exception e) {
throw new RuntimeException("Exception while computing database"
+ " live data.", e);
}
}
if (computed) {
postValue(value);
}
} finally {
mComputing.set(false);
}
}
} while (computed && mInvalid.get());
}
};
final Runnable mInvalidationRunnable = new Runnable() {
#MainThread
#Override
public void run() {
boolean isActive = hasActiveObservers();
if (mInvalid.compareAndSet(false, true)) {
if (isActive) {
getQueryExecutor().execute(mRefreshRunnable);
}
}
}
};
The most obvious thing here is that atomics are being used for everything they are not good at:
Identifying losers and ignoring winners (what reactive patterns need).
AND a happens once behavior, performed by the loser thread.
So this is completely counter intuitive to what atomics are able to achieve, since they are extremely good at defining winners, AND anything that requires a "happens once" becomes impossible to ensure state consistency (the last one is suitable to start a philosophical debate about concurrency, and I will definitely agree with any conclusion).
If atomics are used as: "Contention checkers" and "Contention blockers" then we can implement the exact principle with a volatile check of an atomic reference after a successful CAS.
And checking this volatile against the snapshot/witness during every other step of the process.
private final AtomicInteger invalidationCount = new AtomicInteger();
private final IntFunction<Runnable> invalidationRunnableFun = invalidationVersion -> (Runnable) () -> {
if (invalidationVersion != invalidationCount.get()) return;
try {
T value = computeFunction.call();
if (invalidationVersion != invalidationCount.get()) return; //In case computation takes too long...
postValue(value);
} catch (Exception e) {
e.printStackTrace();
}
};
getQueryExecutor().execute(invalidationRunnableFun.apply(invalidationCount.incrementAndGet()));
In this case, each thread is left with the individual responsibility of checking their position in the contention lane, if their position moved and is not at the front anymore, it means that a new thread entered the process, and they should stop further processing.
This alternative is so laughably simple that my first question is:
Why didn't they do it like this?
Maybe my solution has a flaw... but the thing about the first alternative (the nested spin-lock) is that it follows the idea that an atomic CAS operation cannot be verified a second time, and that a verification can only be achieved with a cmpxchg process.... which is... false.
It also follows the common (but wrong) believe that what you define after a successful CAS is the sacred word of GOD... as I've seen code seldom check for concurrency issues once they enter the if body.
if (mInvalid.compareAndSet(false, true)) {
// Ummm... yes... mInvalid is still true...
// Let's use a second atomicReference just in case...
}
It also follows common code conventions that involve "double-<enter something>" in concurrency scenarios.
So only because the first code follows those ideas, is that I am inclined to believe that my solution is a valid and better alternative.
Even though there is an argument in favor of the "nested spin-lock" option, but does not hold up much:
The first alternative is "safer" precisely because it is SLOWER, so it has MORE time to identify contention at the end of the current of incoming threads.
BUT is not even 100% safe because of the "happens once" thing that is impossible to ensure.
There is also a behavior with the code, that, when it reaches the end of a continuos flow of incoming threads, 2 signals are dispatched one after the other, the second to last one, and then the last one.
But IF it is safer because it is slower, wouldn't that defeat the goal of using atomics, since their usage is supposed to be with the aim of being a better performance alternative in the first place?

Related

How can I block ConcurrentHashMap get() operations during a put()

ConcurrentHashMap<String, Config> configStore = new ConcurrentHashMap<>();
...
void updateStore() {
Config newConfig = generateNewConfig();
Config oldConfig = configStore.get(configName);
if (newConfig.replaces(oldConfig)) {
configStore.put(configName, newConfig);
}
}
The ConcurrentHashMap can be read by multiple threads but can be updated only by a single thread. I'd like to block the get() operations when a put() operation is in progress. The rationale here being that if a put() operation is in progress, that implies the current entry in the map is stale and all get() operations should block until the put() is complete. How can I go about achieving this in Java without synchronizing the whole map?
It surely looks like you can defer this to compute and it will take care for that for you:
Config newConfig = generateNewConfig();
configStore.compute(
newConfig,
(oldConfig, value) -> {
if (newConfig.replaces(oldConfig)) {
return key;
}
return oldConfig;
}
);
You get two guarantees from using this method:
Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple
and
The entire method invocation is performed atomically
according to its documentation.
The accepted answer proposed to use compute(...) instead of put().
But if you want
to block the get() operations when a put() operation is in progress
then you should also use compute(...) instead of get().
That's because for ConcurrentHashMap get() doesn't block while compute() is in progress.
Here is a unit test to prove it:
#Test
public void myTest() throws Exception {
var map = new ConcurrentHashMap<>(Map.of("key", "v1"));
var insideComputeLatch = new CountDownLatch(1);
var threadGet = new Thread(() -> {
try {
insideComputeLatch.await();
System.out.println("threadGet: before get()");
var v = map.get("key");
System.out.println("threadGet: after get() (v='" + v + "')");
} catch (InterruptedException e) {
throw new Error(e);
}
});
var threadCompute = new Thread(() -> {
System.out.println("threadCompute: before compute()");
map.compute("key", (k, v) -> {
try {
System.out.println("threadCompute: inside compute(): start");
insideComputeLatch.countDown();
threadGet.join();
System.out.println("threadCompute: inside compute(): end");
return "v2";
} catch (InterruptedException e) {
throw new Error(e);
}
});
System.out.println("threadCompute: after compute()");
});
threadGet.start();
threadCompute.start();
threadGet.join();
threadCompute.join();
}
Output:
threadCompute: before compute()
threadCompute: inside compute(): start
threadGet: before get()
threadGet: after get() (v='v1')
threadCompute: inside compute(): end
threadCompute: after compute()
This fundamentally doesn't work. Think about it: When the code realizes that the information is stale, some time passes and then a .put call is done. Even if the .put call somehow blocks, the timeline is as follows:
Some event occurs in the cosmos that makes your config stale.
Some time passes. [A]
Your run some code that realizes that this is the case.
Some time passes. [B]
Your code begins the .put call.
An extremely tiny amount of time passes. [C]
Your code finishes the .put call.
What you're asking for is a strategy that eliminates [C] while doing absolutely nothing whatsoever to prevent reads of stale data at point [A] and [B], both of which seem considerably more problematic.
Whatever, just give me the answer
ConcurrentHashMap is just wrong if you want this, it's a thing that is designed for multiple concurrent (hence the name) accesses. What you want is a plain old HashMap, where every access to it goes through a lock. Or, you can turn the logic around: The only way to do what you want is to engage a lock for everything (both reads and writes); at which point the 'Concurrent' part of ConcurrentHashMap has become completely pointless:
private final Object lock = new Object[0];
public void updateConfig() {
synchronized (lock) {
// do the stuff
}
}
public Config getConfig(String key) {
synchronized (lock) {
return configStore.get(key);
}
}
NB: Use private locks; public locks are like public fields. If there is an object that code outside of your control can get a ref to, and you lock on it, you need to describe the behaviour of your code in regards to that lock, and then sign up to maintain that behaviour forever, or indicate clearly when you change the behaviour that your API just went through a breaking change, and you should thus also bump the major version number.
For the same reason public fields are almost invariably a bad idea in light of the fact that you want API control, you want the refs you lock on to be not accessible to anything except code you have under your direct control. Hence why the above code does not use the synchronized keyword on the method itself (as this is usually a ref that leaks all over the place).
Okay, maybe I want the different answer
The answer is either 'it does not matter' or 'use locks'. If [C] truly is all you care about, that time is so short, and pales in comparison to the times for [A] and [B], that if A/B are acceptable, certainly so is C. In that case: Just accept the situation.
Alternatively, you can use locks but lock even before the data ever becomes stale. This timeline guarantees that no stale data reads can ever occur:
The cosmos cannot ever make your data stale.
Your code, itself, is the only causal agent for stale date.
Whenever code runs that will or may end up making data stale:
Acquire a lock before you even start.
Do the thing that (may) make some config stale.
Keep holding on to the lock; fix the config.
Release the lock.
How can I go about achieving this in Java without synchronizing the whole map?
There are some good answers here but there is a simpler answer to use the ConcurrentMap.replace(key, oldValue, newValue) method which is atomic.
while (true) {
Config newConfig = generateNewConfig();
Config oldConfig = configStore.get(configName);
if (!newConfig.replaces(oldConfig)) {
// nothing to do
break;
}
// this is atomic and will only replace the config if the old hasn't changed
if (configStore.replace(configName, oldConfig, newConfig)) {
// if we replaced it then we are done
break;
}
// otherwise, loop around and create a new config
}

Use a semaphore in writer reader

So I'm attending a course in multi threaded development and are currently learning about semaphores. In our latest assignment we are supposed to use three threads and two queues. The writer thread will write chars to the first queue, then a "encryptor" thread will read the chars from that queue, encrypt the char and then add it to the second queue. Then we have a reader thread which reads from the second queue. To handle synchronization we are supposed to use semaphore's and mutex, but I managed without any:
public class Buffer {
private Queue<Character> qPlain = new LinkedList<Character>();
private Queue<Character> qEncrypt = new LinkedList<Character>();
private final int CAPACITY = 3;
public Buffer() {
System.out.println("New Buffer!");
}
public synchronized void addPlain(char c) {
while (qPlain.size() == CAPACITY) {
try {
wait();
System.out.println("addPlain is waiting to add Data");
} catch (InterruptedException e) {
}
}
qPlain.add(c);
notifyAll();
System.out.println("addPlain Adding Data-" + c);
}
public synchronized char removePlain() {
while (qPlain.size() == 0) {
try {
wait();
System.out.println("----------removePlain is waiting to return Data.");
} catch (InterruptedException e) {
}
}
notifyAll();
char c = qPlain.remove();
System.out.println("---------------removePlain Returning Data-" + c);
return c;
}
public synchronized void addEncrypt(char c) {
while (qEncrypt.size() == CAPACITY) {
try {
wait();
System.out.println("addEncrypt is waiting to add Data");
} catch (InterruptedException e) {
}
}
qEncrypt.add(c);
notifyAll();
System.out.println("addEncrypt Adding Data-" + c);
}
public synchronized char removeEncrypt() {
while (qEncrypt.size() == 0) {
try {
wait();
System.out.println("----------------removeEncrypt is waiting to return Data.");
} catch (InterruptedException e) {
}
}
notifyAll();
char c = qEncrypt.remove();
System.out.println("--------------removeEncrypt Returning Data-" + c);
return c;
}
}
So this works fine, but I'm not going to pass as I haven't used any semaphore. I do understand the concept, but I just don't see the point to use any in this case. I have 2 queues and just one reader and writer for each one.
EDIT: Updated with Semaphores instead. It almost works, problem arises when the removePlain() method get's called when the queue is empty. I'm pretty sure I should block it, but I'm lost here. Could I not just use a mutex here instead?
public class Buffer {
private Semaphore encryptedSem = new Semaphore(0);
private Semaphore decryptedSem = new Semaphore(0);
private final Queue<Character> qPlain = new LinkedList<Character>();
private final Queue<Character> qEncrypt = new LinkedList<Character>();
private final int CAPACITY = 3;
private boolean startedWrite = false;
private boolean startedRead = false;
/**
* Adds a character to the queue containing non encrypted chars.
*
* #param c
*/
public void addPlain(char c) {
// Makes sure that this writer executes first.
if (!startedWrite) {
startedWrite = true;
encryptedSem = new Semaphore(1);
}
if (qPlain.size() < CAPACITY) {
aquireLock(encryptedSem);
System.out.println("addPlain has lock");
qPlain.add(c);
realeseLock(encryptedSem);
}
}
/**
* Removes and returns the next char in the non encrypted queue.
*
* #return
*/
public char removePlain() {
// TODO Need to fix what happens when the queue is 0. Right now it just
// returns a char that is 0. This needs to be blocked somehow.
char c = 0;
if (qPlain.size() > 0) {
aquireLock(encryptedSem);
System.out.println("removePlain has lock");
c = qPlain.remove();
realeseLock(encryptedSem);
} else {
System.out.println("REMOVEPLAIN CALLED WHEN qPlain IS EMPTY");
}
return c;
}
/**
* Adds a character to the queue containing the encrypted chars.
*
* #param c
*/
public void addEncrypt(char c) {
if (!startedRead) {
startedRead = true;
decryptedSem = new Semaphore(1);
}
if (qEncrypt.size() < CAPACITY) {
aquireLock(decryptedSem);
System.out.println("addEncrypt has lock");
qEncrypt.add(c);
realeseLock(decryptedSem);
}
}
/**
* Removes and returns the next char in the encrypted queue.
*
* #return
*/
public char removeEncrypt() {
char c = 0;
if (qEncrypt.size() > 0) {
aquireLock(decryptedSem);
System.out.println("removeEncrypt has lock");
c = qEncrypt.remove();
realeseLock(decryptedSem);
}
return c;
}
/**
* Aquries lock on the given semaphore.
*
* #param sem
*/
private void aquireLock(Semaphore sem) {
try {
sem.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Realeses lock on the given semaphore.
*
* #param sem
*/
private void realeseLock(Semaphore sem) {
sem.release();
}
}
OK, so trying to adress your concerns, without doing your homework :-)
About your first sample
At first sight, this is a working sample. You are using a form of mutual exclusion through the synchronized keyword, which allows you to use this.wait/notify correctly. This also provides safeguards seeing every thread synchronizes on the same monitor, which provides adequate happen-before safety.
In other words, thanks to this single monitor, you are assured that anything under the synchronized methods is executed exclusively and that these methods side-effects are visible inside the other methods.
Only minor gripe is that your queues are not final, which according to safe object publication guidelines and depending on how your whole system/threads is bootstraped, might lead to visibility issues. Rule of thumb in multithreaded code (and maybe even generally) : whatever can be made final should be.
The real problem with your code is that it does not fulfill your requirements : use semaphores.
About your second sample
Unsafe boolean mutation
This one has real issues. First, your startedWrite/ startedRead booleans : you mutate them (change their true/false value) outside of any synchronization (lock, semaphores, syncrhonized, ... nothing at all). This is unsafe, under the java memory model it would be legal for a thread that has not performed the mutation to not see the mutated value. Put it another way, the first write could set startedWrite to true, and it could be that all other threads never see that true value.
Some discussions on this :
- https://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html
- Java's happens-before and synchronization
So anything that relies on these booleans is inherently flawed in your sample. That is, your Semaphore assignments, for one thing.
Several ways to correct this :
Always mutate shared state under a synchonization tool of some sort (in your first sample, it was the synchronized keyword, and here it could be your semaphores), and make sure that the same tool is used by all threads mutating or accessing the variable
Or use a concurrently safe type, like AtomicBoolean is this case, which has concurrency guarantees that any mutation is made visible to other threads
Race conditions
Another issue with your second code sample, is that you check the sizes of your queues before taking a lock and modifiying them, that is :
if (qPlain.size() > 0) {
aquireLock(encryptedSem);
...
c = qPlain.remove();
realeseLock(encryptedSem);
} else {
System.out.println("REMOVEPLAIN CALLED WHEN qPlain IS EMPTY");
}
Two concurrent threads could perform the check at the first line at the same time, and behave wrongly. Typical scenario is :
qplain has a size of 1
Thread 1 arrives at the if, checks that qplain is not empty, the check succeeds, then thread 1 is paused by the OS scheduler right here and now
Thread 2 arrives at the same if and the same check succeeds for the same reason
Thread 1 and Thread 2 resume from there on, both think they are allowed to take 1 element out of qplain which is wrong, because qplain has a size of 1 actually.
It will fail. You should have had a mutual exclusion of some sort. You can not (rule of thumb again) perform a check and after it perform a mutation under a lock. Both the check and the mutation should happen in, broadly speaking, the same lock. (Or you are a very advanced multithreading kind of guy and you know optimistic locking and stuf like that really well).
Possible deadlock
Another rule of thumb: any time you acquire and release a lock and/or a resource at the same call site, you should have a try/finally pattern.
That is, no matter how it is done, your code should always look like
acuquireSemaphore();
try {
// do something
} finally {
releaseSemaphore();
}
Same goes for locks, input or output streams, sockets, ... Failure to do so may lead to your semaphore being acquired but never released, especially in case of an uncaught exception. So do use try and finally around your resources.
Conclusions
With such serious flaws, I did not really read your code to see if the "spirit" of it works. Maybe it does, but at this point, it's not worth it to check it out further.
Going forward with your assignment
You are asked to use two tools : Semaphores and mutual exclusion (e.g. synchonized, or Lock I guess). Both are not exactly the same thing!
You probablye get mutual exclusions, as your first sample showed. Probably not Semaphores yet. The point of semaphores, is that they (safely) manage a number of "permits". One (a thread) can ask for a permit, and if the semaphore has one available and grants it, one can proceed with one's work. Otherwise, one is put in a "holding pattern" (a wait) untill a permit is available. At some point, one* is expected to give the permit back to the Semaphore, for others to use.
(*Please note : it is not mandatory for a semaphore to work that threads performing permit acquisition are the one to perform permit release. It is part of what make a lock and a semaphore so different, and it's a good thing).
Let's start simple : a Semaphore that only has one permit can be used as a mutual exclusion. Bonus point : it can be released by another thread than the one that acquired it. That makes it ideal for message passing between threads : they can exchange permits.
What does it remember us of ? Of wait / notify of course!
A possible path to a solution
So we have a semaphore, and it has a number of permits. What could the meaning of this number be ? A natural candidate is : have a Semaphore hold the number of elements inside the queues. At first, this could be zero.
Each time somebody puts an element in the queue, it raises the number of permits by one.
Each time somebody takes an element off the queue, it lowers the number of permits.
Then : trying to take an element off an empty queue means trying to acquire a permit from an empty semaphore, it will automatically block the caller. This seems to be what you want.
But!
We're yet to have a definition for "putting an element on top of a full queue". That is because semaphores are not bounded in permits. One can start with an empty semaphore and call "release" a thousand times, and end up with a 1000 permits available. We wil blow our maximal capacity without any kind of bounds.
Let's say we have a workaround for that, we're still not done : we did not make sure at this point that readers and writers do not modify the queue at the same time. And this is crucial for correctneess !
So we need other ideas.
Well issue #2 is easy : we are allowed to use exclusive locks for this exercie, so we'll use them. Just make sure that any manipulation to the list itself is under a synchonized block using the same monitor.
Issue number one... Well, we have a Semaphore representing a "not empty" condition. That's one of the two pairs of wait/notify you had in your first sample. OK cool, let's make another Semaphore representing a "not full" condition, the other wait/notifyPair of your code sample !
So recap : use a semaphore for each couple of wait/notify in your original sample. Keep a mutual exclusion to actually modify the contents of the queue object. And be very carreful of the articulation of the mutual exclusion part with the semaphores, it is the crux of the matter.
And I'll stop there to let you walk down this path if you want.
Bonus point
You should not have to code the same thing twice here. In your samples, you coded twice the same logic (one for the "clear text", once for the "encrypted"), basically, wait for (at least) a spot before stacking a char, and wait for the presence of (at least) a char before popping a it.
This should be one and the same code / methods. Do it once, and you'll get it right (or wrong of course) at all times. Write it twice, you double the chance of mistakes.
Future thoughts
This is all still very complex, for something that could be done using a `BlockingQueuè but then again, homeworks do have another purpose :-).
A bit more complex, but this message passing pattern of signaling having a thread waiting for a "notEmpty" signal, while the other waits on a "notFull" signal is the exact use case of the JDK Condition object, which mimicks the use of wait/notify.

Brian Goetz's improper publication

The question has been posted before but no real example was provided that works. So Brian mentions that under certain conditions the AssertionError can occur in the following code:
public class Holder {
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if (n!=n)
throw new AssertionError("This statement is false");
}
}
When holder is improperly published like this:
class someClass {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
I understand that this would occur when the reference to holder is made visible before the instance variable of the object holder is made visible to another thread. So I made the following example to provoke this behavior and thus the AssertionError with the following class:
public class Publish {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
public static void main(String[] args) {
Publish publish = new Publish();
Thread t1 = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < Integer.MAX_VALUE; i++) {
publish.initialize();
}
System.out.println("initialize thread finished");
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
int nullPointerHits = 0;
int assertionErrors = 0;
while(t1.isAlive()) {
try {
publish.holder.assertSanity();
} catch(NullPointerException exc) {
nullPointerHits++;
} catch(AssertionError err) {
assertionErrors ++;
}
}
System.out.println("Nullpointerhits: " + nullPointerHits);
System.out.println("Assertion errors: " + assertionErrors);
}
});
t1.start();
t2.start();
}
}
No matter how many times I run the code, the AssertionError never occurs. So for me there are several options:
The jvm implementation (in my case Oracle's 1.8.0.20) enforces that the invariants set during construction of an object are visible to all threads.
The book is wrong, which I would doubt as the author is Brian Goetz ... nuf said
I'm doing something wrong in my code above
So the questions I have:
- Did someone ever provoke this kind of AssertionError successfully? With what code then?
- Why isn't my code provoking the AssertionError?
Your program is not properly synchronized, as that term is defined by the Java Memory Model.
That does not, however, mean that any particular run will exhibit the assertion failure you are looking for, nor that you necessarily can expect ever to see that failure. It may be that your particular VM just happens to handle that particular program in a way that turns out never to expose that synchronization failure. Or it may turn out the although susceptible to failure, the likelihood is remote.
And no, your test does not provide any justification for writing code that fails to be properly synchronized in this particular way. You cannot generalize from these observations.
You are looking for a very rare condition. Even if the code reads an unintialized n, it may read the same default value on the next read so the race you are looking for requires an update right in between these two adjacent reads.
The problem is that every optimizer will coerce the two reads in your code into one, once it starts processing your code, so after that you will never get an AssertionError even if that single read evaluates to the default value.
Further, since the access to Publish.holder is unsynchronized, the optimizer is allowed to read its value exactly once and assume unchanged during all subsequent iterations. So an optimized second thread would always process the same object which will never turn back to the uninitialized state. Even worse, an optimistic optimizer may go as far as to assume that n is always 42 as you never initialize it to something else in this runtime and it will not consider the case that you want a race condition. So both loops may get optimized to no-ops.
In other words: if your code doesn’t fail on the first access, the likeliness of spotting the error in subsequent iterations dramatically drops down, possibly to zero. This is the opposite of your idea to let the code run inside a long loop hoping that you will eventually encounter the error.
The best chances for getting a data race are on the first, non-optimized, interpreted execution of your code. But keep in mind, the chance for that specific data race are still extremely low, even when running the entire test code in pure interpreted mode.

Can we say that by synchronizing a block of code we are making the contained statements atomic?

I want to clear my understanding that if I surround a block of code with synchronized(this){} statement, does this mean that I am making those statements atomic?
No, it does not ensure your statements are atomic. For example, if you have two statements inside one synchronized block, the first may succeed, but the second may fail. Hence, the result is not "all or nothing". But regarding multiple threads, you ensure that no statement of two threads are interleaved. In other words: all statements of all threads are strictly serialized, even so, there is no guarantee, that all or none statements of a thread gets executed.
Have a look at how Atomicity is defined.
Here is an example showing that the reader is able to ready a corrupted state. Hence the synchronized block was not executed atomically (forgive me the nasty formatting):
public class Example {
public static void sleep() {
try { Thread.sleep(400); } catch (InterruptedException e) {};
}
public static void main(String[] args) {
final Example example = new Example(1);
ExecutorService executor = newFixedThreadPool(2);
try {
Future<?> reader = executor.submit(new Runnable() { #Override public void run() {
int value; do {
value = example.getSingleElement();
System.out.println("single value is: " + value);
} while (value != 10);
}});
Future<?> writer = executor.submit(new Runnable() { #Override public void run() {
for (int value = 2; value < 10; value++) example.failDoingAtomic(value);
}});
reader.get(); writer.get();
} catch (Exception e) { e.getCause().printStackTrace();
} finally { executor.shutdown(); }
}
private final Set<Integer> singleElementSet;
public Example(int singleIntValue) {
singleElementSet = new HashSet<>(Arrays.asList(singleIntValue));
}
public synchronized void failDoingAtomic(int replacement) {
singleElementSet.clear();
if (new Random().nextBoolean()) sleep();
else throw new RuntimeException("I failed badly before adding the new value :-(");
singleElementSet.add(replacement);
}
public int getSingleElement() {
return singleElementSet.iterator().next();
}
}
No, synchronization and atomicity are two different concepts.
Synchronization means that a code block can be executed by at most one thread at a time, but other threads (that execute some other code that uses the same data) can see intermediate results produced inside the "synchronized" block.
Atomicity means that other threads do not see intermediate results - they see either the initial or the final state of the data affected by the atomic operation.
It's unfortunate that java uses synchronized as a keyword. A synchronized block in Java is a "mutex" (short for "mutual exclusion"). It's a mechanism that insures only one thread at a time can enter the block.
Mutexes are just one of many tools that are used to achieve "synchronization" in a multi-threaded program: Broadly speaking, synchronization refers to all of the techniques that are used to insure that the threads will work in a coordinated fashion to achieve a desired outcome.
Atomicity is what Oleg Estekhin said, above. We usually hear about it in the context of "transactions." Mutual exclusion (i.e., Java's synchronized) guarantees something less than atomicity: Namely, it protects invariants.
An invariant is any assertion about the program's state that is supposed to be "always" true. E.g., in a game where players exchange virtual coins, the total number of coins in the game might be an invariant. But it's often impossible to advance the state of the program without temporarily breaking the invariant. The purpose of mutexes is to insure that only one thread---the one that is doing the work---can see the temporary "broken" state.
For code that use syncronized on that object - yes.
For code, that don't use syncronized keyword for that object - no.
Can we say that by synchronizing a block of code we are making the contained statements atomic?
You are taking a very big leap there. Atomicity means that the operation if atomic will complete in one CPU cycle or equivalent to one CPU cycle whereas Synchronizing a block means only one thread can access the critical region. It may take multiple CPU cycles for processing code in the critical region(which will make it non atomic).

ReentrantLock and Condition, best design

I am playing around with Conditions in ReentrantLock in the context of a resource pool, from what I can see it simplifies thread communications. My questions is, I end up organically writing strange Conditionals such as acquiredMapEmpty, freeQueueNotEmpty, change that await and single different things. Technically they can be all replaced by one Conditional or be broken up into more Conditionals -- is there a rule of rule of thumb for:
Identifying the Conditionals
Figuring out if you have too many or too few
When your on the right track or way off course
Here is example of removing a resource.
public boolean remove(R resource) throws InterruptedException {
System.out.println("Remove, resource: " + resource + " with thread: " + Thread.currentThread().getName());
if (resource == null) {
throw new NullPointerException();
}
mainLock.lock();
try {
if (!isOpen) {
throw new IllegalStateException("Not open");
}
Object lock = locks.get(resource);
if (lock == null) {
return false;
}
if (freeQueue.remove(resource)) {
locks.remove(resource);
if (!freeQueue.isEmpty()) {
freeQueueNotEmpty.signalAll();
}
return true;
}
while (!freeQueue.contains(resource)) {
change.await();
if (!isOpen) {
throw new IllegalStateException("Not open");
}
lock = locks.get(resource);
if (lock == null) {
return false;
}
}
if (freeQueue.remove(resource)) {
locks.remove(resource);
if (!freeQueue.isEmpty()) {
freeQueueNotEmpty.signalAll();
}
return true;
}
return false;
} finally {
mainLock.unlock();
}
}
Well, as a rule of thumb, I tend to have as many condition variables as there are reasons for a thread to block. The rationale is that when you signal a condition variable you want to wake up a thread that is waiting for that specific change in the state you're signalling and you really, really want to avoid the "thundering herd" syndrome - waking up all the threads, blocked on a condition, only to have one of them make progress and all the others going back to sleep, having wasted precious CPU time and thrashed caches in the meantime.
In my humble opinion, there is no thumb rule here.
It really depends on use-cases, and synchronization is not an easy topic at all.
Of course you should not "exhaust" your system with locks - locks are an expensive resource.
If you feel you need to coordinate threads, and to protected shared resources, then you have no choice but to use synchronization objects.
Each time you use a synch object such as a lock or a condition that is obtained from a lock, you should ask yourself what is the use-case, do you really need the lock, what other threads need to be coordinated (what are their flows).
I want to take this question a bit off-topic and give you an example - in which I discovered that we have several threads using synchronized keyword, but some perform read, and some write, so I switched to ReaderWriterLock - so should be your case,
don't use all kinds of synch-objects just cause they are cool - carefully understand if and where they are really needed.

Categories