For the heck of it (and some entertainment value) let's see what might be feasable ...
I put the lock object into a static field of a package-visible class, letting all my default methods share the lock. A lock provider provides instances their own lock on-demand. The lock is removed from the collection when the instance is garbage collected.
The lock provider creates a lock the first time it is requested from an instance and then returns the same lock thereafter. It looks like this:
final class LockProvider {
private static final WeakHashMap<Widget,Object> widgetLocks = new WeakHashMap<>();
static Object obtainLock(Widget w) {
synchronized (widgetLocks) {
return locks.computeIfAbsent(w, x -> new Object());
}
}
}
And now the default interface method looks like this:
public interface Widget{
default void addSomething(List<String> names) {
synchronized (LockProvider.obtainLock(this)) {
... do something
}
}
}
One weakness of this is that the WeakHashMap
uses Object.hashcode()
and Object.equals()
. Another is that, although fast, it is not super-high-performance. Although this way of doiung it seems clever ... any method that requires synchronization on a private lock would be better designed in another way.
[UPDATED]
What I did in the end was:
1) create default methods:
public interface Widget{
default void addSomething(List<String> something) {
... do something
}
}
2) Then created both regular and thread-safe implementations
public class WidgetImpl implements Widget{
...
}
// Threadsafe version
public class WidgetThreadsafeImpl implements Widget{
private final Object lock = new Object();
public void addSomething(List<String> something) {
synchronized(lock){
super.addSomething(something);
}
}
}
The default method provides an algorithm and the implementations can provide the thread-safe or non-thread-safe implementations.