Edit: I led your code through a ten-thread test suite, adding and comparing thousands of templates, but I could not find anything wrong with your implementation. I believe that you are misinterpreting your data. For example, in a streaming environment, this sometimes returns false:
// sometimes this can be false blacklist.contains(pattern) == blacklist.contains(pattern);
Another thread changed the blacklist between the first call, but before the second call. This is normal behavior, and the class itself cannot do anything to stop it. If this is not the behavior you want, you can synchronize it outside the class:
synchronized (blacklist) {
Original answer:
You are synchronizing the root of the node, but this does not synchronize any of its children. All you need to do to make your bulletproof class synchronize the add(int[]) and contains(int[]) methods and then not leak any links. This ensures that only one thread can ever use a Blacklist object.
I was looking for your code trying to figure it out, so you can also use it:
import java.util.HashMap; import java.util.Map; import java.util.Stack; public class Blacklist { private final Node root = new Node(Integer.MIN_VALUE, false); public synchronized void add(int[] array) { if (array == null) return; Node next, cur = root; for(int i = 0; i < array.length-1 && !cur.isLeaf(); i++) { next = cur.getChild(array[i]); if (next == null) { next = new Node(array[i], false); cur.addChild(next); } cur = next; } if (!cur.isLeaf()) { next = cur.getChild(array[array.length-1]); if (next == null || !next.isLeaf()) cur.addChild(new Node(array[array.length-1], true)); } } public synchronized boolean contains(int[] array) { if (array == null) return false; Node cur = root; for (int i = 0; i < array.length; i++) { cur = cur.getChild(array[i]); if (cur == null) return false; if (cur.isLeaf()) return true; } return false; } private static class Node { private final Map<Integer, Node> children; private final int value; public Node(int _value, boolean leaf) { children = (leaf?null:new HashMap<Integer, Node>()); value = _value; } public void addChild(Node child) { children.put(child.value, child); } public Node getChild(int value) { return children.get(value); } public boolean isLeaf() { return (children == null); } } }
Collection structure can make you a lot easier. You do not make any benefits by overriding ArrayList.
Here I use HashMap, so you do not need to allocate more than 9000 links for something like this:
blacklist.add(new int[] {1, 2000, 3000, 4000});