Menu

Synchronized Collections

Synchronized Collections from Happy-Libraries differs from standard JDK-implementation of such methods like java.util.Collections. synchronizedCollection(…) by providing a lock-object to the public API. In that way structured hierarchies like tree can be synchronized with a single lock.

package org.happy.examples.collections.decorators.examples.synchronize;

import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.apache.commons.lang.NotImplementedException;
import org.happy.collections.sets.decorators.EventSet_1x0;
import org.happy.collections.sets.decorators.SynchronizedSet_1x2;
import org.happy.commons.concurrent.ConcurrentUtils_1x2;
import org.happy.commons.patterns.Lockable_1x0;
import org.happy.commons.patterns.observer.event.ActionEventBefore_1x0;
import org.happy.commons.patterns.observer.listener.ActionListener_1x0;

import com.google.common.base.Preconditions;

/**
 * SynchNode can be used in Multi-Threaded code. All children will be synchronized around the same lock.
 * This example presents a simple Multi-Thread acyclic Tree implementation. 
 * The nodes in the tree will be validated before they can be added as child to any existing nodes.
 * @author Andreas Hollmann
 *
 */
public class SynchNode implements Lockable_1x0{

    public static void main(String[] args) {

        ExecutorService executor = ConcurrentUtils_1x2.createCachedExecutorService("SynchNode-Example");

        final Object lock = new Object();
        SynchNode a = new SynchNode("A",lock);
        SynchNode b = new SynchNode("B", lock);
        a.getChildren().add(b);
        a.getChildren().add(new SynchNode("C", lock));

        try{
            a.getChildren().add(a);
            throw new IllegalStateException("can't reach this code!");
        }
        catch(IllegalArgumentException e){
            //ok
            System.out.println("Expected Exception thrown: A can't be added to itself...");
        }

        try{
            b.getChildren().add(a);
            throw new IllegalStateException("can't reach this code!");
        }
        catch(IllegalArgumentException e){
            //ok
            System.out.println("Expected Exception thrown: A can't be added to childNodes of itSelf...");
        }
        System.out.println(a);

        executor.shutdownNow();
    }

    private String name;
    private SynchNode parent;
    private Set<SynchNode> children;
    private Object lockObject;

    protected SynchNode(String name, Object lock) {
        super();
        Preconditions.checkNotNull(name);
        this.name = name;

        Preconditions.checkNotNull(lock);
        this.lockObject = lock;

        final LinkedHashSet<SynchNode> set = new LinkedHashSet<>();

        /*
         * before any elements willl be added an event will be fired, thus new elements can be validated
         */
        final EventSet_1x0<SynchNode> eventSet = EventSet_1x0.of(set);
        eventSet.getOnBeforeAddEvent().add(new ActionListener_1x0<ActionEventBefore_1x0<SynchNode>>() {
            @Override
            public void actionPerformedImpl(ActionEventBefore_1x0<SynchNode> event) {
                SynchNode node = event.getData();
                Preconditions.checkNotNull(node);//new node can't be null
                validateNodeForCycle(node);
                node.setParent(SynchNode.this);
            }

        } );

        /*
         * synchronize all hierarchy same lock
         */
        this.children = SynchronizedSet_1x2.of(eventSet, lock);
    }

    protected void validateNodeForCycle(SynchNode node) throws IllegalArgumentException{
        synchronized (this.lockObject) {

            /*
             * validates the node for cycles and if there any cycles an exception will be fired
             */
            if(this.equals(node) || this.children.contains(node)){
                throw new IllegalArgumentException("the node " + node.toString() + " can't be added as child to node: " + this.toString() + " because it produces a cycle!");
            }

            final SynchNode parent = this.getParent();
            if(parent!=null){
                parent.validateNodeForCycle(node);
            }
        }
    }

    public Set<SynchNode> getChildren() {
        return children;
    }

    @Override
    public Object getLockObject() {
        return this.lockObject;
    }

    @Override
    public void setLockObject(Object lockObject) {
        throw new NotImplementedException();
    }

    public SynchNode getParent() {
        return parent;
    }

    protected void setParent(SynchNode parent) {
        this.parent = parent;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SynchNode other = (SynchNode) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "SynchNode [name=" + name + ", children=" + children + "]";
    }

}

The output of the main() is:

Expected Exception thrown: A can't be added to itself...
Expected Exception thrown: A can't be added to childNodes of itSelf...
SynchNode [name=A, children=[SynchNode [name=B, children=[]], SynchNode [name=C, children=[]]]]