Weak Listeners

Resource Deallocation in Java

Resource deallocation in Java is no problem since Java has automatic garbage collection.

Right! …? …?? …Wrong!

Since Java has automatic garbage collection, resource deallocation is often taken care of for you,
but when it is not, it becomes a real pain.
Since the language relies so heavily on garbage collection, it seems the language
designers forgot to include good mechanisms for the cases where garbage collection does
not solve the problem (releasing external resources) or just does not happen automatically.

A common scenario for memory leaks in Java is Listener registration and de-registration.
When you want one component to react to events in another component you usually use a listener
pattern. The listening component registers itself as a callback client to an observable component.

Finding the right place to register the listener usually is not a problem.
Often it is done in the constructor of the listening component or in some setup
method.

Unregistering however is often not so obvious since there is no canonical tear down
mechanism in Java. You are often left with listeners staying registered in their
observables and therefore not eligable for garbage collection.

Netbeans Solution

The Netbeans developers have addressed this problem by providing a class called
WeakListener. It works by inserting a WeakListener object between the listener
and the observable. The listener is only referenced weakly (with a WeakReference)
and therefore is automatically collected, when other references to the listener
go away. The WeakListener class also unregisters itself automagically when it
notices that its concrete listener has been collected. Unregistering the WeakListener
is only possible, if the observable conforms to certain conventions.

This solution is great, when you have a given model and want to make sure, that
your listeners do not remain referenced indefinitely.

My Solution

My solution works the other way around. The observable has support for weak listeners
built in. It keeps its list of listeners in a WeakListener support class. Listeners
can register themselves in the usual way. They may unregister themselves explicitly
or just count on being disconnected automatically.

This solution is great, when you are designing a model (observable) and want to
make life easier for your clients (listeners).

Example

Observable

public class NumberModel {

    private WeakNumberChangedListeners listeners =
            new WeakNumberChangedListeners();

    private int theNumber = 0;
    private String name;

    public NumberModel(String name)
    {
        this.name = name;
    }

    public synchronized void inc()
    {
        int oldNumber = theNumber;
        theNumber++;
        listeners.fire (new NumberChangedEvent(this, oldNumber, theNumber));
    }

    public synchronized void dec()
    {
        int oldNumber = theNumber;
        theNumber--;
        listeners.fire (new NumberChangedEvent(this, oldNumber, theNumber));
    }

    public synchronized int getNumber ( )
    {
        return theNumber;
    }

    public String getName()
    {
        return name;
    }

    public void addNumberChangedLisener (NumberChangedListener listener)
    {
        listeners.add (listener);
    }

    public void removeNumberChangedListener (NumberChangedListener listener)
    {
        listeners.remove (listener);
    }
}

Listener Interface

public interface NumberChangedListener {

    void numberChanged (NumberChangedEvent event);

}

WeakListeners class

public class WeakNumberChangedListeners
        extends WeakListeners<NumberChangedListener, NumberChangedEvent>
{

    @Override
    protected void fireOne(NumberChangedListener listener, NumberChangedEvent event)
    {
        listener.numberChanged (event);
    }

}
}

WeakListeners.java

package com.bininda.weaklistener;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;

/**
 * A list of listeners that are only referenced weakly.
 * Listeners are registered using the add method. They may be deregistered
 * using the remove method, however if the listener is garbage collected, because
 * it is not hard referenced any more, it is automatically removed.
 * The method fireOne must be overwritten in a derived class.
 * @param I The listener interface
 * @param E The event type
 * @author bininda
 */
public abstract class WeakListeners<I, E> {

    /**
     * Weak map of references to listeners.
     * WeakHashMap has the nice feature of automatically getting rid of
     * stale entries.
     */
    private Set<I> wSet = Collections.newSetFromMap(new WeakHashMap<I, Boolean>());

    /**
     * Create an empty weak listener list.
     */
    public WeakListeners()
    {

    }

    /**
     * Add a new listener to the list of registered listeners.
     * If the listener has already been added, the only effect is diagnostic
     * output.
     * @param listener The new listener
     */
    public synchronized void add (I listener)
    {
        wSet.add (listener);
    }

    /**
     * Remove a registered listener from the list.
     * If the listener has already been removed, output diagnostics.
     * Note that automatic removal can not happen as long as someone still has
     * a hard reference to the listener and is therefore able to call this method.
     * @param listener
     */
    public synchronized void remove (I listener)
    {
        wSet.remove (listener);
    }

    /**
     * Fire one event to one registered listener.
     * @param listener The listener to fire to
     * @param event The event to pass to the listener.
     */
    abstract protected void fireOne(I listener, E event);

    /**
     * Fire an event to all registered listeners.
     * If diagnostics are enabled, listeners that have been collected automatically
     * are printed out.
     * @param event The event to fire to the listeners.
     */
    public synchronized void fire(E event)
    {
        {
            List<I> lcp = new LinkedList<I>(wSet);
            for (I l: lcp)
            {
                try
                {
                    fireOne (l, event);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

}

Leave a Reply

Your email address will not be published. Required fields are marked *