Thomas said:
I read through this post, and I'm not sure that you do. Maybe "mostly".
Nope, I'm afraid it is "completely" ;-)
Pondering this, I'm trying to work out where our disagreement lies.
But before getting into it, I want to mention that I don't think your example
of "protecting" access to an external resource, like writing unscrambled lines
to System.out, is relevant here. I'll come back to why later, but I think
it'll be easier to understand my position if you know in advance that I don't
consider such cases to be of any great importance for the point in question.
In another post you mentioned a hierarchy which I'll paraphrase (and extend) as
something like:
I am protecting these lines of code,
in order to protect this aspect of shared state,
in order that my program will work correctly,
.. skipping a level or two here...
in order that I may enjoy enhanced wealth and status...
where, at each level, the goal can be seen as a tactic that is intended to
contribute to achieving the goal of the next level.
Looked at in this way, one could think of the disagreement between us as merely
a matter of which of the first two levels we see as critical in day-to-day
programming. A question of which way of thinking about it worked best for the
individual.
I take a very object-centric view of OO programming, which can be summed up in
a slogan: "the objects matter, the code doesn't". (A POV, by the way, that has
been greatly strengthened by my more recent experience with Smalltalk and
Smalltalk IDEs.) From that you'd expect that I would prefer to put the
emphasis on level two in the above list (the "state" here being understood as
the objects). Whereas it seems that you prefer to take the first level as the
"real" one.
I do think that some part of our disagreement may come from that sort of
difference in attitude, but I want to go further and say that the first level
is actively misleading. The rest of this post will be an unstructured ramble
through my reasons.
(This is probably as good a point as any to mention that if I were interviewing
prospective junior programmers, and they said that synchronisation was to
protect passages of code, then I'd very likely fail them on that basis alone --
at the very least it would ring loud alarm bells and I would put even more
effort into trying to discover if they really understood what objects were.
It's up to you, of course, to decide how much weight to give to my opinion, but
such facts /in general/ should surely inform how you train your students. (I
am right in thinking you teach ?))
The first point is that (as you know, of course) synchronised blocks (and
methods) don't /really/ protect passages of code -- not unless you
conceptualise the code as being duplicated "in" each object. I think that if
you put the emphasis on the code in this way, then you are actively inviting
the mistake of forgetting that the synchronisation is bound to the object. As
a result you need a counter-balancing way of forcing a sense of /individual/
objects back into the picture; which you try to do by encouraging explicit
synchronisation and using separate lock/monitor objects. That seems to me to
be wrong -- if you hadn't lost sight of the objects in the first place, then
the issue wouldn't arise. You are, in a sense, treating the symptoms, not the
disease.
BTW, if I were teaching concurrent Java programming -- which, thank God, I'm
not -- I'd be tempted to try the experiment of ensuring that /every single/
example the students ever saw (or created) had at least two instances of the
class(es) in question. I don't know if you've ever tried that, maybe you do it
as a matter of course, but it'd be interesting to know if it helped. It might
even be worth trying in teaching ordinary, non-threaded, programming... (I'd
also postpone teaching "static" for as long as I possible could -- but that's
another story...)
A further digression: the thing I dislike most about Java as a language is that
it does little to encourage the object-centric viewpoint, and does little to
challenge -- indeed makes it hard to escape -- the code-centric viewpoint.
Perhaps we could see "synchronized" as another opportunity to teach /why/ it's
important to think about the objects. "Objects matter, code doesn't"...
Anyway, returning to the question in hand; my second point is about
responsibility in OO. Another slogan:
IT IS AN OBJECT'S RESPONSIBILITY TO MANAGE ITS OWN STATE.
(I'm sorry about the shouting, and I hope I didn't hurt anyone's ears, but I do
think it is important enough to warrant a little noise.) In a concurrent
program that may include maintaining the integrity of the state in the face of
concurrent access (some objects assume that responsibility, some -- e.g. Java
Collections -- don't). Nearly all uses of Java's concurrency features are
directly linked to allowing an object to satisfy the responsibilities it has
assumed for permitting concurrent use. I think it's important to describe and
think about synchronisation in a way that puts emphasis what it's (normally)
being used for. That's to say that I think the "synchronisation protects
objects" viewpoint is, or should be, the most natural and fruitful for the OO
programmer.
In fact I'm strongly tempted to go further and say that if that /isn't/ what
it's being used for then there's something wrong with the design of the
program. I'm not certain that there aren't count-examples, though.
I similarly think it's important to program in a way that reflects what
synchronisation is for. I have no particular preference between synchronised
methods and synchronized (on 'this', whether implicitly or explicitly) blocks,
but using a separate (and un-necessary) lock object is just obfuscating the
design.
(But I have to be honest and admit that in Smalltalk -- where objects don't all
have the Gosling-given ability to act as monitors -- it doesn't seem to be
significantly more difficult to read or write threadsafe code, even though you
/have/ to use separate lock objects. It's partly a matter of language-specific
idiom, and following familiar patterns where possible.)
Imagine a variant of Java where synchronisation was only allowed on 'this' (so
it only had synchronised methods, and unqualified "synchronized { ... }"
blocks). I think that nearly all concurrent code would be unaffected by the
change, and that the "feel" of concurrent programming in that language would be
just the same as in Java itself. Of course some expressions and designs
wouldn't carry over unchanged, but most of them would. Yet in that language it
would be inescapably true that synchronisation protects the objects -- that's
all it /could/ do. Since that language would be essentially the same as Java,
I think that supports the idea that "synchronisation protects the objects" in
Java too.
Incidentally, the above is why I don't think that your examples of cases where
'synchronized" /isn't/ protecting any obvious object, or anything that would
naturally be called "state", are very relevant (although perfectly valid). If
such cases are rare (as they are) and if they can be handled gracefully in the
modified Java (as they could be) then (IMO) it's clear that they are not
central for understanding either what Java synchronisation /is/ or what it's
/for/. In short, they are a distraction (and a source of confusion for
students).
In the Java variant, your example of writing ungarbled lines to System.out
could not be solved directly. Two possible approaches would be to implement a
Semaphore class (with the classic P and V operations), or to create a new
object that "knew" its job was to accept data piecemeal and write it out in
coherent lines. In either case, what you have done is create a new object that
explicitly accepts responsibility for "managing" access to the resource. To me
that seems like a better design than unstructured "ah-hoc" synchronisation.
Returning to the case where several objects need to coordinate so as to
maintain the coherency of their shared state. This is, of course, something
that can be handled by using shared lock (or "monitor") objects. But I wonder
how many such cases would be better handled by finding higher level ways of
expressing the coordination. Using higher-level abstractions seems better to
me than "synchronization spaghetti".
To finish with yet another digression: this is why I wouldn't be particularly
bothered if a junior programmer, who did understand objects and "synchronized"
correctly, did not know about "synchronized(someOtherObject) {". There are
simply too few legitimate places to use it, and is so easy to explain if the
basics are solid, that I can't see that it matters much. (Mind you, I would
worry about what /other/ gaps there were in that programmer's training...)
-- chris