Proper behaviour under concurrent access is a complex topic, and it's not as simple as just slapping synchronized
on everything, as now you have to think about how operations might interleave.
For instance, imagine you have a class like a list, and you want to make it threadsafe. So you make all the methods synchronized and continue. Chances are, clients might be using your list in the following way:
int index = ...; // this gets set somewhere, maybe passed in as an argument
// Check that the list has enough elements for this call to make sense
if (list.size() > index)
{
return list.get(index);
}
else
{
return DEFAULT_VALUE;
}
In a single-threaded environment this code is perfectly safe. However, if the list is being accessed (and possibly modified) concurrently, it's possible for the list's size to change after the call to size()
, but before the call to get()
. So the list could "impossibly" throw an IndexOutOfBoundsException (or similar) in this case, even though the size was checked beforehand.
There's no shortcut of how to fix this - you simply need to think carefully about the use-cases for your class/interface, and ensure that you can actually guarantee them when interleaved with any other valid operations. Often this might require some additional complexity, or simply more specifics in the documentation. If the hypothetical list class specified that it always synchronized on its own monitor, than that specific situation could be fixed as
synchronized(list)
{
if (list.size() > index)
{
return list.get(index);
}
}
but under other synchronization schemes, this would not work. Or it might be too much of a bottleneck. Or forcing the clients to make the multiple calls within the same lexical scope may be an unacceptable constraint. It all depends on what you're trying to achieve, as to how you can make your interface safe, performant and elegant.