"Thomas G. Marshall" <
[email protected]>
wrote in message /snip/
As I pointed out already, I too am worried about the potential lack of
documentation concerning the locking details within an object.
But your interface expansion notion does not hold for existing classes
beyond your control. Say you need to pull something out of a collection or
even a GUI table in one section and move it to another? This operation:
Entry entry = thang.grab(someplace);
thang.set(entry, elsewhere);
Is hugely thread unsafe. If you don't do this:
synchronized(thang)
{
Entry entry = thang.grab(someplace);
thang.set(entry, elsewhere);
}
then you might wish to make sure that all access goes through another method
of your own construction elsewhere...
//unified control to Things
public class SafeThingMutator
{
// you prefer synced methods, so ok, I'll use a general
// operation sync supplied by another poster...
public static synchronized
void modify(Runnable operation)
{
operation.run();
}
}
...but what if the Thing is part of a larger Swing GUI element that makes
its /own/ modifications to Thing on its own. /That/ wouldn't go through
your protection routine.
These are common issues. What would you do?
The "correct" solution is to put the "smarts" into
the object itself so that the client only tells
the object *what* to do, not *how* to do it.
Your "thang" object should have a separate method
that accepts the "someplace" and "elsewhere" parameters,
and performs the entire movement without the client
knowing the details of synchronization. Encapsulation
is a *good* thing.
When the client is forced to specify *how* to do
something, that is procedural programming, rather
than object-oriented programming. It also runs
the risk of breaking subclasses or general
contracts between the client and the class.
In most cases, when the class does not provide a method
to do the new procedural task, the class can be extended
to a subclass that can perform the procedural task. Or
a new class can be written that encapsulates the other
class with composition. Knowledge of the implementation
details of the class are centralized in one place, and
the client can then use the new method calls in many
places. If the implementation needs changing, it's only
changed in one place and the client's method calls are
unaffected.
OTOH, if the client was forced to sprinkle those
"synchronized(thang){}" blocks everywhere, it would
be a major headache to fix all those blocks when the
implementation details change sufficiently to break
the client's view of that implementation.
A simple-minded example is a client that has
an ArrayList and wants to create an array of
the elements. The client knows that all of
elements are a subtype of "Fubar".
{
Fubar[] fubars;
synchronized(arrayList)
{
final int kk;
kk = arrayList.size();
fubars = new Fubar[kk];
arrayList.toArray(fubars);
}
}
In the above code-snippet, the "arrayList"
variable refers to an ArrayList that is
shared by multiple threads. The client code
is forced to synchronize the list before
extracting the size and then extracting the
elements.
A better solution is to extend the ArrayList
class to add a new method:
public class MyArrayList
extends ArrayList
{
public Fubar[] getFubars()
{
Fubar[] result;
synchronized(this)
{
final int kk;
kk = size();
result = new Fubar[kk];
toArray(result);
}
return result;
}
}
Now, the client application can use an instance
reference type of MyArrayList, instead of an ArrayList.
Instead of a hard-coding synchronized(arrayList){}
blocks everywhere, the client simply calls the getFubars()
method, which performs the necessary synchronization
and returns the array. The client must use the
MyArrayList type, instead of a plain ArrayList
type.
btw: The MyArrayList class should probably use
the generics extension "extends ArrayList<Fubar>"
to ensure that the client only uses subtypes of
Fubar for the array elements. But that's another
topic...
