I remember Neal Gafter's talk about his idea of how to implement closures in Java that he gave at TSSJS in Las Vegas this March, and I have to say, if we had this feature today, it'd be none too soon.
Lack of closures does give Java a reeeally bad feel in 2007. It makes it very backwards looking in terms of modern programming language amenities, compared to languages that already have them.
Here, let me show you an example.
As we all know, in Java, objects can't control their own locking - they can't customize their behavior when used in a synchronized block. Now, that's one entertaining problem in its own right, and would deserve its very own discussion, as basically, it makes correctness of synchronization dependent on implementation. But that's not the point of this post. The point of this post is how one might try to work around this and similar lackings, and how one will inevitably fail...
Okay, the example: let's suppose I have a class A. I sometimes need to lock instances of it. This is a no-brainer in Java and usually has the form of
synchronized(a) {
... do something...
}
Let's suppose I also have a subclass B of A. Instances of B have a reference to another object of some class S, and various instances of B share some of their state through shared instances of S for some reason:
public class B extends A {
private final S shared;
...
}
Since the S instance is actually shared among several B instances, the correct locking semantics for B would be to always synchronize on the "shared" field of a B, when client code synchronizes on an instance of B. Unfortunately, there's no way in Java to declare to do a synchronized(b.shared) within every synchronized(b).
What's the next best thing I can do - provided I want to encapsulate the behavior and want to avoid some manual monster code1 at every synchronization site? A smart aleck like me would add a method to A:
public void runLocked(Runnable r) {
synchronized(this) {
r.run();
}
}
Then, I can override it in B:
@Override
public void runLocked(Runnable r) {
synchronized(this) {
synchronized(shared) {
r.run();
}
}
}
Finally, I can replace all occurrences of a synchronized(a) block with:
a.runLocked(new Runnable() {
public void run() {
... do something...
}
});
Sounds okay? Well, it isn't. Aside from the very obvious "too much visual clutter" problem, there are further problems with this:
- What if "do something" throws a checked exception? (Answer: more monster code2)
- What if "do something" modifies a local variable? (Answer: yet more monster code3)
- What if "do something" contains a return, continue, or break statement? (Answer: you are seriously out of luck, but some really horrid monster code might help you out. It is so ugly though that it does question the ROI of the whole approach. No, I won't give an example.)
So, what happens is that you eliminate ugliness in one place, only to have it resurface because of these other problems in another place. It is not really possible to arrive at a win-win situation with current language constructs. (Alternatively, I'm not smart enough to figure it out. Either way, pity). Needless to say, if we had real closures in Java instead of the very weak imitation attempt in form of java.lang.Runnable, none of these would be a problem.
1 the monster code in question would look something like:
synchronized(a) {
if(a instanceof B) {
synchronized(((B)a).getShared()) {
... do something ...
}
} else {
... repeat do something ...
}
}
2 the monster code for working around checked exceptions would be:
try {
a.runLocked(new Runnable() {
public void run() {
try {
... do something ...
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new UndeclareThrowableException(e);
}
}
});
} catch(UndeclaredThrowableException e) {
Throwable t = e.getCause();
if(t instanceof Exception) throw (Exception)e;
if(t instanceof Error) throw (Error)e;
throw e;
}
And it can get even more fun if you're trying to only get a specific type of a checked exception (say, IOException) and not a generic Exception across the scope.
3 the monster code for working out local variable modifications would be:
final Object[] embeddedLocalVar = new Object[1];
embeddedLocalVar[0] = realLocalVar;
a.runLocked(new Runnable() {
public void run() {
... do something ...
embeddedLocalVar[0] = newValue;
}
});
realLocalVar = embeddedLocalVar[0];
Pure beauty, innit?
2 comments:
Hm ...I must be missing something. As "shared" is private B will never have access to it. If you provide access to it - that's where you would need to do the synchronization. No?
"shared" is indeed a private variable, but it's an object reference, and it's possible that several instances of B have their "shared" variable point to some same - external to them, but shared - object.
Post a Comment