6

I have a number of default methods in interfaces that need synchronization and it seems that only this is available:

default void addUniqueColumns(List<String> names) {
    synchronized (this) {
        ... do something
    }
}

The problem is, I want to synchronize on a private lock instead of this for better control:

default void addUniqueColumns(List<String> names) {
    synchronized (lock) {  // how to get a private lock in a default method??
        ... do something
    }
}

Solutions? Clever workarounds? Or just live with it :) !

The Coordinator
  • 13,007
  • 11
  • 44
  • 73

3 Answers3

2

You can put the lock object into a pubic static field of a package-visible class, letting all your default methods share the lock. The lock remains visible inside your library, but since classes with default access are not visible outside your library, the lock would be private to the users of your interface outside your library:

class LockHolder { // Package private class
    public static Object LOCK = new Object();
}

public interface ColumnCollection {
    default void addUniqueColumns(List<String> names) {
        synchronized (LockHolder.LOCK) {
            ... do something
        }
    }
}

As far as your library is concerned as a whole, this trick gives you the same advantages as using a private lock object does compared to synchronizing on this, because it prevents malicious code written by outsiders from accessing your lock. Of course the lock can be grabbed by any part of your library.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    That is an interesting approach. Though, I am not sure I want a lock than can be shared within the package. I get that this is probably an impossibility and the methods will have to be moved to abstract classes. – The Coordinator Dec 01 '13 at 13:48
  • @SaintHill There is no absolute protection in Java, at least not as long as it has reflection. The choice is yours - abstract class gives you far less flexibility, but the lock could be private to the class. Package-private static members, on the other hand, could be avoided with an abstract class. – Sergey Kalinichenko Dec 01 '13 at 19:16
  • Interestingly enough I was actually asking myself a few hours ago why someone would try to do this with default methods and not with plain old abstract classes. What's the advantage? – mwhs Dec 01 '13 at 20:38
  • @mwhs That's a great question. [I wrote something up about extension methods in C# a few years ago](http://dasblinkenlight.blogspot.com/2008/12/extension-methods-that-microsoft-added.html), the same reasoning applies to Java's default methods. – Sergey Kalinichenko Dec 01 '13 at 22:55
  • @dasblinkenlight So default methods basically tell the developer to stop abusing/distorting inheritance to avoid redundancy? That's pretty cool. The other day I was thinking about how many patterns in OOD exist that actually deal with just avoiding redundancy or enforcing DRY. – mwhs Dec 02 '13 at 07:09
  • I am going to vote this as the answer. Because it is simple and also safe from an api point of view, and it does not solve a more complex locking issue which should really be left out of interface methods from the start. So .. if an api 'default' implementation requires it, then make it safe by putting it in a package private class like this example. – The Coordinator Mar 17 '15 at 03:14
1

You could add a getLock() method to your interface and have each implementor return the object to lock over.

ksasq
  • 4,424
  • 2
  • 18
  • 10
  • That wouldn't be a *private* lock object that the OP wants, though, very much defeating the purpose. – Sergey Kalinichenko Dec 01 '13 at 11:40
  • Fair point, although I don't think it's possible to do that within an interface. The lock inside the implementor can, of course, be private though. – ksasq Dec 01 '13 at 11:41
0

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.

The Coordinator
  • 13,007
  • 11
  • 44
  • 73
  • "You should fix your design and use an abstract class rather than an interface instead of inventing such kludges. " Do I hear an echo?? Read the preamble! As for the synchronize .. There I fixed it. :) – The Coordinator Dec 04 '13 at 22:39
  • 1
    Yes, you heard an echo. Some things are too important to be told just one time. ;^) Btw. if going for such a workaround, the best solution would be using a concurrent map (which provides an atomic `computeIfAbsent`) together with a custom key class derived from `WeakReference`, wrapping the Widget and using `System.identityHashCode` of the widget as its own `hashCode`. This would solve all flaws at once. – Holger Dec 05 '13 at 09:12
  • @Holger smart idea! Feel free to edit the answer here with your fix. Or I will when I get a chance. – The Coordinator May 06 '14 at 07:39