Does partial streaming security make the Java class thread-safe?

I came across the following Java class example, which is claimed to be thread safe. Can someone explain how it can be thread safe? I can clearly see that the last method in the class is not protected from concurrent access to any read stream. Or am I missing something here?

public class Account { private Lock lock = new ReentrantLock(); private int value = 0; public void increment() { lock.lock(); value++; lock.unlock(); } public void decrement() { lock.lock(); value--; lock.unlock(); } public int getValue() { return value; } } 
+5
source share
6 answers

Short answer:

By definition, Account is a thread-safe class, even if the geValue method geValue not protected

Long answer

From Java Concurrency, in practice, a class is called thread safe when:

No operations performed sequentially or at the same time, instances of a thread-safe class can cause the instance to be in the wrong state.

Since the getValue method getValue not cause the Account class to be in an invalid state at any given time, your class is called thread safe.

The documentation for Collection # synchronizedCollection resonates with this feeling:

Returns a synchronized (thread safe) collection supported by the specified collection. To ensure consistent access, it is critical that all access to the support collection is through the returned collection. It is imperative that the user manually synchronize the returned collection when repeating this:

  Collection c = Collections.synchronizedCollection(myCollection); ... synchronized (c) { Iterator i = c.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); } 

Note that the documentation states that the collection (which is an object of the inner class named SynchronizedCollection in the Collections class) is thread safe and still asks for a client code to protect the collection when it repeats. Infact, the iterator method in SynchronizedCollection not synchronized . This is very similar to your example where Account is thread safe, but client code should still be atomic when calling getValue .

+1
source

The code is not thread safe.

Suppose one thread calls decrement , and then the second thread calls getValue . What's happening?

The problem is that there is no “happen before” relationship between decrement and getValue . This means that there is no guarantee that a getValue call will see decrement results. Indeed, getValue can "skip" the results of an undefined sequence of invocation increment and decrement .

Actually, if we do not see the code that uses the Account class, the issue of thread safety is not defined. A common concept of protection against program stream 1 is whether the code behaves correctly, regardless of the non-determinism associated with the stream. In this case, we do not have a specification of what the “correct” behavior is, or a really executable program for testing or verification.

But my reading of code 2 is that there is an alleged API requirement / validity criterion that getValue returns the current value of the account. This cannot be guaranteed if there are multiple threads, so the class is not thread safe.

Related links:


1 - The Concurrency Order in Practice in the @CKing answer also refers to the concept of “correctness”, mentioning the “incorrect state” in the definition. However, JLS sections on the memory model do not determine thread safety. Instead, they talk about "well-formed executions."

2 - This reading is supported by the OP comment below. However, if you do not agree that this requirement is real (for example, because it is not indicated explicitly), then the flip side is that the behavior of the "account" abstraction depends on how the code is outside the Account class .. which makes it an "leaky abstraction."

+6
source

This is not thread safe just because there are no guarantees as to how the compiler can reorder. Since the value is not mutable, here is your classic example:

 while(account.getValue() != 0){ } 

It can be raised to look like

 while(true){ if(account.getValue() != 0){ } else { break; } } 

I can imagine that there are other permutations in the compiler that can lead to its failure. But accessing this getValue through multiple threads may cause a crash.

+2
source

There are several different issues here:

Q: If multiple threads make overlapping calls to increment() and decrement() , and then they stop, and then enough time, without threads calling increment() or decrement() , getValue () returns the correct number

A: Yes. Blocking increment and decrement methods ensures that every increment and decrement operation will occur atomically. They cannot interfere with each other.


Q: How long is enough time?

A: It's hard to say. The Java language specification does not guarantee that the thread calling getValue () will never see the last value written by some other thread, because getValue () accesses the value without any synchronization whatsoever.

If you modify getValue () to lock and unlock the same lock object, or if you declare count equal to volatile , then a zero amount of time will suffice.


Q: Can calling getValue() return an invalid value?

A: No. It can return only the initial value or the result of a full call to increment() or the result of the full operation decrement() .

But the reason for this has nothing to do with blocking. Locking does not prevent any thread from calling getValue() , while some other thread is in the middle of increasing or decreasing the value.

What prevents getValue () from returning a completely invalid value is an int value, and JLS ensures that updates and readings of int variables are always atomic.

+2
source

This is a completely safe stream.

No one can increase and decrease value at the same time so that you don't lose or get an error counter.

The fact that getValue() will return different values ​​over time is what happens anyway: simultaneity doesn't matter.

0
source

You do not need to protect getValue. Access to it from several streams at the same time does not lead to any negative consequences. The state of an object cannot become invalid regardless of how many or how many threads you call this method (since it does not change).

Having said that, you can write unsafe code that uses this class.

For example, something like

 if (acc.getValue()>0) acc.decrement(); 

is potentially dangerous as it can lead to race conditions. What for?

Let's say you have a business rule, "never decrease below 0", your current value is 1, and there are two threads that execute this code. There will be a chance that they will do this in the following order:

  • In thread 1, it is checked that acc.getValue> 0. Yes!

  • Theme 2, that acc.getValue> 0. Yes!

  • Thread 1 causes a decrement. value is 0

  • Thread 2 causes decrement. value is now -1

What happened? Each function guaranteed that it would not be below zero, but together they were able to do it. This is called race conditions.

To avoid this, you should not protect elementary operations, but rather any pieces of code that must be executed without interruption.

So this class is thread safe, but only for very limited use.

0
source

All Articles