Prev Next

Listeners Considered Harmful - The “Whiteboard” Pattern - Comparison


Comparison

The following table provides a line-by-line code comparison of the implementation with the listener versus the implementation with the whiteboard using Declarative Services.

Table 1: Listeners versus Whiteboard

Listener Pattern Whiteboard Pattern
public interface Display {
  void addContentProvider( ContentProvider p );
  void removeContentProvider( ContentProvider p );
}
public interface ContentProvider {
    String getContent();
}
public interface ContentProvider {
    String getContent();
}

class Clock implements ContentProvider {
    private final Display display;
    Clock(Display display) {
        this.display = display;
        display.addContentProvider(this);
    }
    void dispose() {
        display.removeContentProvider(this);
    }
    public String getContent() { return new Date().toString(); }
}
@Component
public class Clock implements ContentProvider {

    public Clock() {}






    public String getContent() { return new Date().toString(); }
}
class DisplayTracker extends ServiceTracker<Display, Clock> {
    DisplayTracker(BundleContext context) {
        super(context, Display.class, null);
    }
    public Clock addingService(ServiceReference<Display> ref) {
        Display display = context.getService(ref);
        return new Clock(display);
    }
    public void removedService(ServiceReference<Display> ref, Clock clock) {
        clock.dispose();
        context.ungetService(ref);
    }
}
public class ClockManager implements BundleActivator {
    private DisplayTracker tracker;
    public void start(BundleContext context) {
        tracker = new DisplayTracker(context);
        tracker.open();
    }
    public void stop(BundleContext context) {
        tracker.close();
    }
}
class ContentProviderRegistration implements Display {
    private final List<ContentProvider> providers;
    private final DisplayManager manager;
    ContentProviderRegistration(DisplayManager manager) {
        this.manager = manager;
        providers = new ArrayList<>();
    }
    public synchronized void addContentProvider(ContentProvider p) {
        providers.add(p);
        manager.addContentProvider(p);
    }
    public synchronized void removeContentProvider(ContentProvider p) {
        if (providers.remove(p)) {
            manager.removeContentProvider(p);
        }
    }
    void synchronized dispose() {
        for (ContentProvider p : providers) {
            manager.removeContentProvider(p);
        }
        providers.clear();
    }
}
class DisplayFactory implements ServiceFactory<Display> {
    private final DisplayManager manager;
    DisplayFactory(DisplayManager manager) {
        this.manager = manager;
    }
    public Display getService(Bundle b, ServiceRegistration<Display> r) {
        return new ContentProviderRegistration(manager);
    }
    public void ungetService(Bundle b, ServiceRegistration<Display> r, Display s) {
        ContentProviderRegistration cpr = (ContentProviderRegistration) s;
        cpr.dispose();
    }
}

public class DisplayManager implements BundleActivator, Runnable {
    private volatile Thread thread;
    private ServiceRegistration<Display> registration;
    private final List<ContentProvider> providers = new ArrayList<>();

    public void start(BundleContext context) {
        DisplayFactory factory= new DisplayFactory(this);
        registration = context.registerService(Display.class, factory, null);
        thread = new Thread(this, "DisplayManager Listener");
        thread.start();
    }

    public void stop(BundleContext context) {
        thread = null;
    }
    public void run() {
        Thread current = Thread.currentThread();
        int n = 0;
        while (current == thread) {
            synchronized (providers) {
                if (!providers.isEmpty()) {
                    if (n >= providers.size())
                        n = 0;
                    ContentProvider cp = providers.get(n++);
                    System.out.println("LISTENER: " + cp.getContent());
                }
            }
            try { Thread.sleep(5000); } catch(InterruptedException e) {}
        }
    }
    void addContentProvider(ContentProvider p) {
        synchronized (providers) {
            providers.add(p);
        }
    }
    void removeContentProvider(ContentProvider p) {
        synchronized (providers) {
            providers.remove(p);
        }
    }
}
@Component(service = {})
public class DisplayManager implements Runnable {
    private volatile Thread thread;
    @Reference
    private volatile List<ContentProvider> providers;
    @Activate
    void activate() {


        thread = new Thread(this, "DisplayManager Component");
        thread.start();
    }
    @Deactivate
    void deactivate() {
        thread = null;
    }
    public void run() {
        Thread current = Thread.currentThread();
        int n = 0;
        while (current == thread) {
            List<ContentProvider> providers = this.providers;
            if (!providers.isEmpty()) {
                if (n >= providers.size())
                    n = 0;
                ContentProvider cp = providers.get(n++);
                System.out.println("COMPONENT: " + cp.getContent());
            }

            try { Thread.sleep(5000); } catch(InterruptedException e) {}
        }
    }










}

Obviously, the whiteboard approach is a significantly smaller and simpler implementation especially since we use Declarative Services to manage the lifecycle of the components and their use of services. It is even simpler than the code size suggests. What is not obvious is the fact that the listener pattern has significantly more deadlock possibilities than the whiteboard pattern.

See GitHub for the example code above.