Home > Software engineering >  Can object having private fields only all methods synchronized be named as thread safe?
Can object having private fields only all methods synchronized be named as thread safe?

Time:09-27

Can object having private fields only all methods synchronized be named as thread safe?

public class Person {

private String name = "John";
private byte age = 30;
private Object somefield;
// synchronized getters and setters

}

CodePudding user response:

No.

Thread safety as a concept applied to an entire class isn't a boolean proposition (it's not "this is thread safe" and "this is not thread safe"). There is no such thing as a thread safe class. There is only such a thing as a thread safe action.

Here's a trivial example. Let's say you want a hashmap, but 'thread safe' (as in, that the entire object is considered 'thread safe'. Java certainly appears to allow you to create such a beast:

Map<Integer, String> map = Collections.synchronizedMap(new HashMap<Integer, String>());

But.. no. Here's a simple operation that is not thread safe, using the obvious API endpoints to do this, at least, obvious until java 8 which introduced a whole bunch of improvements:

if (!map.containsKey(1)) map.put(1, calculateValueAssociatedWith(1));

This isn't thread safe. After all, perhaps right in the middle (after checking if key 1 is in the map but before adding a mapping for '1'), another thread injects something. Or even, perhaps calculateValueAssociatedWith takes a very long time, and thus 814 threads all arrive at this code within the same 5 minute window or so, they all see that the map does not contain 1, they all start the expensive calculation (813 of those are wasting a ton of CPU cycles and who knows what), and in the end all 814 write the same mapping over and over again into this map. Or worse, let's say the app is inconsistent (broken) if more than one thread works like this.

The problem here is that the actual operation you wanted to perform is: "Calculate a key/value mapping, but.. only if that isn't already in the map - atomically". But that's not what you wrote. That's in fact not even a thing you can write with a single method call, and synchronized only makes thread safe single method calls, not a chain of them, at least, not until java8 (or was it 11?) brought .computeIfAbsent and friends, and I don't even know if they work correctly with synchronizedMap.

The java8 right way to make this operation thread safe (see how you can talk about thread safe operations but not thread safe objects?) is something like this:

synchronized (map) {
  if (!map.containsKey(1)) map.put(1, calculateFor(1));
}

More trivially, your Person object isn't final, so anybody can write a subclass that adds a non-synchronized thing, so that's a second reason why it wouldn't be. But let's paper over that and say you did declare it as final, and all fields are private, and all methods that read or write those fields in any way are all marked synchronized. This sounds like one heck of an inefficient take on things, but, let's roll with it: That only works if there is just no notion whatsoever of a 'conjoined operation', where a task can only be performed by invoking more than one method on your Person instance.

Which is never going to happen, hence, the very idea that you would say 'instances are thread safe' is highly misleading.

Unfortunately, various JVMs do have a habit of writing their docs this way. Make no mistake, those are awkward oversimplifications. What they are really attempting to state is that the individual methods are, but not that any operations you do to them are.

Contrast to various classes in the java.util.concurrent package. These aren't just comprised solely of 'thread safe methods', but their entire API is specifically designed for you to perform all operations in a thread safe manner. Partly by having a ton of methods to capture common tasks that you want to be atomic (so not just .containsKey and .put, but also .computeIfAbsent, merge, etc), and partly by having a design that is more fundamentally compatible with being used from multiple threads. For example CopyOnWriteList.

Generally, trying to share state between 2 running threads is intractably complicated. No amount of tossing synchronized keywords at your source files are going to magically make it all disappear, until you get to the logical extreme where at all times, all threads but one are always paused, and your threads are mostly useless now.

The usual solution is to.. not do that. Don't share state between threads casually. Find the few things that truly must be shared and arrange for all reading and writing of this state to go through very particular mechanisms that are highly specialized for the exact task required of them. This can involve using fork/join or picking the right data abstraction in j.u.c (which you should consider as a collection of things that are useful for tasks that come up a lot specifically for multithreaded code that can't avoid sharing state, thus, it's an excellent place to start first) - or a more generalized and much simpler take which is to do all communication through a medium that is designed for it.

Such as databases (which have transactions), or message queues. Why do you think most high-available web service systems do ALL state in databases, leaving absolutely nothing in the server process? It's not just so you can trivially run multiple servers simultaneously. It's because sharing state between connections is much, much simpler this way, and most databases can do a vastly more optimized job at sharing state than the rather blunt tool of 'just block all the things until this is done'.

CodePudding user response:

It makes sense to call an object "thread safe" if its documentation describes some useful thing that the object can do regardless of how many threads concurrently access it. An example might be a thread-safe queue. The documentation could promise that every object that goes in eventually comes out, that no object comes out more times than it went in, that nothing ever comes out that didn't go in, that if you can prove that object A went in before object B went in, then you can depend on object A coming out before object B comes out, etc. And, all of these promises hold true, no matter how many threads are calling its put() and take() methods at-will with no other coordination between them.

What useful thing does your Person class do that will not be affected by multiple threads all calling its getters and its setters at-will, with no other coordination between them?

CodePudding user response:

The simple answer is "Yes" if all of it is synchronized on the same object. This is a trivial case for a simple object such as what you have presented as an example.

See Collections.synchronizedMap for an example of what this means. All access to the internal state of the object you have defined would be thread-safe. Please read this JavaDoc and understand.

However, as others have posted, there are lots of other conditions such as Collection-Views returned by methods of this object, or inheritance etc. that need to be visited to ensure an action remains thread-safe.

  • Related