package trees.lockbased; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import contention.abstractions.CompositionalMap; /** * Implementation of concurrent AVL tree based on the paper * "Practical Concurrent Binary Search Trees via Logical Ordering" by * Dana Drachsler (Technion), Martin Vechev (ETH) and Eran Yahav (Technion). * * Copyright 2013 Dana Drachsler (ddana [at] cs [dot] technion [dot] ac [dot] il). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * @author Dana Drachsler */ public class LogicalOrderingAVL<K, V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, CompositionalMap<K, V> { /** The tree's root */ private AVLMapNode<K,V> root; /** The keys' comparator */ private Comparator<? super K> comparator; /** A constant object for the use of the {@code insert} method. */ private final static Object EMPTY_ITEM = new Object(); public LogicalOrderingAVL() { AVLMapNode parent = new AVLMapNode(Integer.MIN_VALUE); root = new AVLMapNode(Integer.MAX_VALUE, null, parent, parent, parent); root.parent = parent; parent.right = root; parent.succ = root; } /** * Constructor, initialize the tree and the logical ordering layouts. * The logical ordering is initialized by creating two nodes, where their * keys are the minimal and maximal values. * The tree layout is initialized by setting the root to point to the node * with the maximal value. * * @param min The minimal value * @param max The maximal value */ public LogicalOrderingAVL(final K min, final K max) { AVLMapNode<K,V> parent = new AVLMapNode<K,V>(min); root = new AVLMapNode<K, V>(max, null, parent, parent, parent); root.parent = parent; parent.right = root; parent.succ = root; } /** * Constructor, initialize the tree and the logical ordering layouts. * The logical ordering is initialized by creating two nodes, where their * keys are the minimal and maximal values. * The tree layout is initialized by setting the root to point to the node * with the maximal value. * * @param min The minimal value * @param max The maximal value * @param comparator The keys' comparator */ public LogicalOrderingAVL(K min, K max, Comparator<? super K> comparator) { this(min, max); this.comparator = comparator; } /** * Given some object, returns an appropriate {@link Comparable} object. * If the comparator was initialized upon creating the tree, the * {@link Comparable} object uses it; otherwise, assume that the given * object implements {@link Comparable}. * * @param object The object * @return The appropriate {@link Comparable} object */ @SuppressWarnings("unchecked") private Comparable<? super K> comparable(final Object object) { if (object == null) throw new NullPointerException(); if (comparator == null) return (Comparable<? super K>)object; return new Comparable<K>() { final Comparator<? super K> compar = comparator; final K obj = (K) object; public int compareTo(final K other) { return compar.compare(obj, other); } }; } /** * Traverses the tree to find a node with the given key. * * @see java.util.Map#get(java.lang.Object) */ final public V get(final Object key) { final Comparable<? super K> value = comparable(key); AVLMapNode<K,V> node = root; AVLMapNode<K,V> child; K val; int res = -1; while (true) { if (res == 0) break; if (res > 0) { child = node.right; } else { child = node.left; } if (child == null) break; node = child; val = node.key; res = value.compareTo(val); } while (res < 0) { node = node.pred; val = node.key; res = value.compareTo(val); } while (res > 0) { node = node.succ; val = node.key; res = value.compareTo(val); } if (res == 0 && node.valid) { return (V) node.item; } return null; } /** * Traverses the tree to find a node with the given key. * * @see java.util.Map#containsKey(java.lang.Object) */ @Override final public boolean containsKey(final Object key) { final Comparable<? super K> value = comparable(key); AVLMapNode<K,V> node = root; AVLMapNode<K,V> child; int res = -1; K val; while (true) { if (res == 0) break; if (res > 0) { child = node.right; } else { child = node.left; } if (child == null) break; node = child; val = node.key; res = value.compareTo(val); } while (res < 0) { node = node.pred; val = node.key; res = value.compareTo(val); } while (res > 0) { node = node.succ; val = node.key; res = value.compareTo(val); } return (res == 0 && node.valid); } /** * @see java.util.Map#put(java.lang.Object, java.lang.Object) */ @Override public V put(K key, V value) { return insert(key, value, false, false, null); } /** * @see java.util.concurrent.ConcurrentMap#putIfAbsent(java.lang.Object, java.lang.Object) */ @Override public V putIfAbsent(K key, V value) { return insert(key, value, true, false, null); } /** * @see java.util.concurrent.ConcurrentMap#replace(java.lang.Object, java.lang.Object) */ @Override public V replace(K key, V value) { return insert(key, value, false, true, EMPTY_ITEM); } /** * @see java.util.concurrent.ConcurrentMap#replace(java.lang.Object, java.lang.Object, java.lang.Object) */ @Override public boolean replace(K key, V oldValue, V newValue) { return insert(key, newValue, false, true, oldValue).equals(oldValue); } /** * Insert the pair (key, item) to the tree. * If the key is already present, update the item if putIfAbsent equals {@code false}. * If {@code isReplace} equals {@code true}, the operation takes place only * if the key is already present. Before applying the replacement, the * operation considers the {@code replaceItem}. If this item equals * {@code EmptyItem}, the replacement is applied without considering the * current item associated with that key. Otherwise, the replacement is * applied only if the current item equals to {@code replaceItem}. * * @param key The key * @param item The item * @param putIfAbsent Keep the old item if key is already present? * @param isReplace Is the operation should only take place if the key is already present? * @param replaceItem The item to consider upon replacement. * @return The item that was associated with the given key, or null if the * key was not present in the tree */ final private V insert(final K key, final V item, boolean putIfAbsent, boolean isReplace, Object replaceItem) { final Comparable<? super K> value = comparable(key); AVLMapNode<K,V> node = null; K nodeValue = null; int res = -1; while (true) { node = root; AVLMapNode<K,V> child; res = -1; while (true) { if (res == 0) break; if (res > 0) { child = node.right; } else { child = node.left; } if (child == null) break; node = child; nodeValue = node.key; res = value.compareTo(nodeValue); } final AVLMapNode<K,V> pred = res > 0 ? node : node.pred; pred.lockSuccLock(); if (pred.valid) { final K predVal = pred.key; final int predRes = pred== node? res: value.compareTo(predVal); if (predRes > 0) { final AVLMapNode<K,V> succ = pred.succ; final K succVal = succ.key; final int res2 = succ == node? res: value.compareTo(succVal); if (res2 <= 0) { if (res2 == 0) { V item2 = (V) succ.item; if (!putIfAbsent && (!isReplace || replaceItem.equals(EMPTY_ITEM) || succ.item.equals(replaceItem))) { succ.item = item; } pred.unlockSuccLock(); return item2; } if (isReplace) { pred.unlockSuccLock(); return null; } final AVLMapNode<K,V> parent = chooseParent(pred, succ, node); final AVLMapNode<K,V> newNode = new AVLMapNode<K,V>(key, item, pred, succ, parent); succ.pred = newNode; pred.succ = newNode; pred.unlockSuccLock(); insertToTree(parent, newNode, parent == pred); return null; } } } pred.unlockSuccLock(); } } /** * Choose and lock the correct parent, given the new node's predecessor, * successor, and the node returned from the traversal. * * @param pred The predecessor * @param succ The successor * @param firstCand The node returned from the traversal * @return The correct parent */ final private AVLMapNode<K,V> chooseParent(final AVLMapNode<K,V> pred, final AVLMapNode<K,V> succ, final AVLMapNode<K,V> firstCand) { AVLMapNode<K,V> candidate = firstCand == pred || firstCand == succ? firstCand: pred; while (true) { candidate.lockTreeLock(); if (candidate == pred) { if (candidate.right == null) { return candidate; } candidate.unlockTreeLock(); candidate = succ; } else { if (candidate.left == null) { return candidate; } candidate.unlockTreeLock(); candidate = pred; } Thread.yield(); } } /** * Update the tree layout by connecting the new node to its parent. * Then, the parent's height is updated, and {@link #rebalance} is called. * * @param parent The new node's parent * @param newNode The new node * @param isRight Is the new node should be the parent's right child? */ final private void insertToTree(final AVLMapNode<K,V> parent, final AVLMapNode<K,V> newNode, final boolean isRight) { if (isRight) { parent.right = newNode; parent.rightHeight = 1; } else { parent.left = newNode; parent.leftHeight = 1; } if (parent != root) { AVLMapNode<K, V> grandParent = lockParent(parent); rebalance(grandParent, parent, grandParent.left == parent); } else { parent.unlockTreeLock(); } } /** * Lock the given node's parent. * The operation begins by first reading the node's parent from the node, * then acquiring the parent's lock, and then checking whether this is the * correct parent. If not, the lock is released, and the operation restarts. * * @param node The node * @return The node's parent (which is locked) */ final private AVLMapNode<K,V> lockParent(final AVLMapNode<K,V> node) { AVLMapNode<K, V> parent = node.parent; parent.lockTreeLock(); while (node.parent != parent || !parent.valid) { parent.unlockTreeLock(); parent = node.parent; while (!parent.valid) { Thread.yield(); parent = node.parent; } parent.lockTreeLock(); } return parent; } /** * @see java.util.Map#remove(java.lang.Object) */ @Override final public V remove(final Object key) { return remove(key, false, null); } /** * @see java.util.concurrent.ConcurrentMap#remove(java.lang.Object, java.lang.Object) */ @Override final public boolean remove(final Object key, final Object item) { return remove(key, true, item) != null; } /** * Remove the given key from the tree. * If the flag {@code compareItem} equals true, remove the key only if the * node is associated with the given item. * * @param key The key to remove * @param compareItem The flag that indicates whether to consider the given * item * @param item The given item * @return The item of the node that was removed, or null if no node was * removed */ final public V remove(final Object key, final boolean compareItem, final Object item) { Comparable<? super K> value = comparable(key); AVLMapNode<K,V> pred, node = null; K nodeValue = null; int res = 0; while (true) { node = root; AVLMapNode<K,V> child; res = -1; while (true) { if (res == 0) break; if (res > 0) { child = node.right; } else { child = node.left; } if (child == null) break; node = child; nodeValue = node.key; res = value.compareTo(nodeValue); } pred = res > 0 ? node : node.pred; pred.lockSuccLock(); if (pred.valid) { final K predVal = pred.key; final int predRes = pred== node? res: value.compareTo(predVal); if (predRes > 0) { AVLMapNode<K,V> succ = pred.succ; final K succVal = succ.key; int res2 = succ == node? res: value.compareTo(succVal); if (res2 <= 0) { if (res2 != 0 || (compareItem && !succ.item.equals(item))) { pred.unlockSuccLock(); return null; } succ.lockSuccLock(); AVLMapNode<K,V> successor = acquireTreeLocks(succ); AVLMapNode<K, V> succParent = lockParent(succ); succ.valid = false; V succItem = (V) succ.item; AVLMapNode<K, V> succSucc = succ.succ; succSucc.pred = pred; pred.succ = succSucc; succ.unlockSuccLock(); pred.unlockSuccLock(); removeFromTree(succ, successor, succParent); return succItem; } } } pred.unlockSuccLock(); } } /** * Acquire the treeLocks of the following nodes: * <ul> * <li> The given node * <li> The node's child - if the given node has less than two children * <li> The node's successor, and the successor's parent and child - if the * given node has two children * </ul> * * @param node The given node * @return The node's successor, if the node has two children, and null, * otherwise */ final private AVLMapNode<K,V> acquireTreeLocks(final AVLMapNode<K,V> node) { while (true) { node.lockTreeLock(); final AVLMapNode<K,V> right = node.right; final AVLMapNode<K,V> left = node.left; if (right == null || left == null) { if (right != null && !right.tryLockTreeLock()) { node.unlockTreeLock(); Thread.yield(); continue; } if (left != null && !left.tryLockTreeLock()) { node.unlockTreeLock(); Thread.yield(); continue; } return null; } final AVLMapNode<K,V> successor = node.succ; final AVLMapNode<K, V> parent = successor.parent; if (parent != node) { if (!parent.tryLockTreeLock()) { node.unlockTreeLock(); Thread.yield(); continue; } else if (parent != successor.parent || !parent.valid) { parent.unlockTreeLock(); node.unlockTreeLock(); Thread.yield(); continue; } } if (!successor.tryLockTreeLock()) { node.unlockTreeLock(); if (parent != node) parent.unlockTreeLock(); Thread.yield(); continue; } final AVLMapNode<K,V> succRightChild = successor.right; // there is no left child to the successor, perhaps there is a right one, which we need to lock. if (succRightChild != null && !succRightChild.tryLockTreeLock()) { node.unlockTreeLock(); successor.unlockTreeLock(); if (parent != node) parent.unlockTreeLock(); Thread.yield(); continue; } return successor; } } /** * Removes the given node from the tree layout. * If the node has less than two children, its successor, {@code succ}, is * null, and the removal is applied by connecting the node's parent to the * node's child. Otherwise, the successor is relocated to the node's location. * * @param node The node to remove * @param succ The node's successor * @param parent The node's parent */ private void removeFromTree(AVLMapNode<K, V> node, AVLMapNode<K, V> succ, AVLMapNode<K, V> parent) { if (succ == null) { AVLMapNode<K, V> right = node.right; final AVLMapNode<K,V> child = right == null ? node.left : right; boolean left = updateChild(parent, node, child); node.unlockTreeLock(); rebalance(parent, child, left); return; } AVLMapNode<K, V> oldParent = succ.parent; AVLMapNode<K, V> oldRight = succ.right; updateChild(oldParent, succ, oldRight); succ.leftHeight = node.leftHeight; succ.rightHeight = node.rightHeight; AVLMapNode<K, V> left = node.left; AVLMapNode<K, V> right = node.right; succ.parent = parent; succ.left = left; succ.right = right; left.parent = succ; if (right != null) { right.parent = succ; } if (parent.left == node) { parent.left = succ; } else { parent.right = succ; } boolean isLeft = oldParent != node; boolean violated = Math.abs(succ.getBalanceFactor()) >= 2; if (!isLeft) { oldParent = succ; } else { succ.unlockTreeLock(); } node.unlockTreeLock(); parent.unlockTreeLock(); rebalance(oldParent, oldRight, isLeft); if (violated) { succ.lockTreeLock(); int bf = succ.getBalanceFactor(); if (succ.valid && Math.abs(bf) >=2) { rebalance(succ, null, bf >=2? false: true); } else { succ.unlockTreeLock(); } } } /** * Given a node, {@code parent}, its old child and a new child, update the * old child with the new one. * * @param parent The node * @param oldChild The old child * @param newChild The new child * @return true if the old child was a left child */ private boolean updateChild(AVLMapNode<K, V> parent, AVLMapNode<K, V> oldChild, final AVLMapNode<K, V> newChild) { if (newChild != null) { newChild.parent = parent; } boolean left = parent.left == oldChild; if (left) { parent.left = newChild; } else { parent.right = newChild; } return left; } /** * Rebalance the tree. * The rebalance is done by traversing the tree (starting from the given * node) and applying rotations when detecting imbalanced nodes. * * @param node The node to begin the traversal from * @param child The node's child * @param isLeft Is the given child a left child? */ final private void rebalance(AVLMapNode<K,V> node, AVLMapNode<K,V> child, boolean isLeft) { if (node == root) { node.unlockTreeLock(); if (child != null) child.unlockTreeLock(); return; } AVLMapNode<K,V> parent = null; try { while (node != root) { boolean updateHeight = updateHeight(child, node, isLeft); int bf = node.getBalanceFactor(); if (!updateHeight && Math.abs(bf) < 2) return; while (bf >= 2 || bf <= -2) { if ((isLeft && bf <= -2) || (!isLeft && bf >= 2)) { if (child != null) child.unlockTreeLock(); child = isLeft? node.right : node.left; if (!child.tryLockTreeLock()) { child = restart(node, parent); if (!node.treeLock.isHeldByCurrentThread()) { return; } parent = null; isLeft = node.left == child; bf = node.getBalanceFactor(); continue; } isLeft = !isLeft; } if ((isLeft && child.getBalanceFactor() < 0) || (!isLeft && child.getBalanceFactor() > 0)) { AVLMapNode<K,V> grandChild = isLeft? child.right : child.left; if (!grandChild.tryLockTreeLock()) { child.unlockTreeLock(); child = restart(node, parent); if (!node.treeLock.isHeldByCurrentThread()) { return; } parent = null; isLeft = node.left == child; bf = node.getBalanceFactor(); continue; } rotate(grandChild, child, node, isLeft); child.unlockTreeLock(); child = grandChild; } if (parent == null) { parent = lockParent(node); } rotate(child, node, parent, !isLeft); bf = node.getBalanceFactor(); if (bf >= 2 || bf <= -2) { parent.unlockTreeLock(); parent = child; child = null; isLeft = bf >= 2? false: true; // enforces to lock child continue; } AVLMapNode<K, V> temp = child; child = node; node = temp; isLeft = node.left == child; bf = node.getBalanceFactor(); } if (child != null) { child.unlockTreeLock(); } child = node; node = parent != null && parent.treeLock.isHeldByCurrentThread()? parent: lockParent(node); isLeft = node.left == child; parent = null; } } finally { if (child != null && child.treeLock.isHeldByCurrentThread()) { child.unlockTreeLock(); } if (node.treeLock.isHeldByCurrentThread()) node.unlockTreeLock(); if (parent != null && parent.treeLock.isHeldByCurrentThread()) parent.unlockTreeLock(); } } /** * Release all current treeLocks (of the given node and its parent), and * re-acquire the treeLocks of node and its child. * * @param node The node * @param parent The node's parent * * @return The node's (locked) child */ final private AVLMapNode<K,V> restart(AVLMapNode<K,V> node, AVLMapNode<K,V> parent) { if (parent != null) { parent.unlockTreeLock(); } node.unlockTreeLock(); Thread.yield(); while (true) { node.lockTreeLock(); if (!node.valid) { node.unlockTreeLock(); return null; } AVLMapNode<K, V> child = node.getBalanceFactor() >= 2? node.left : node.right; if (child == null) return null; if (child.tryLockTreeLock()) return child; node.unlockTreeLock(); Thread.yield(); } } /** * Update the height of the given node, based on the given child. * * @param child The node's child * @param node The node * @param isLeft Is the child a left child? * * @return true if the height was updated, and false otherwise */ final private boolean updateHeight(AVLMapNode<K,V> child, AVLMapNode<K,V> node, boolean isLeft) { int newHeight = child == null? 0: Math.max(child.leftHeight, child.rightHeight) + 1; int oldHeight = isLeft? node.leftHeight : node.rightHeight; if (newHeight == oldHeight) return false; if (isLeft) { node.leftHeight = newHeight; } else { node.rightHeight = newHeight; } return true; } /** * Apply a single rotation to the given node. * * @param child The node's child * @param node The node to rotate * @param parent The node's parent * @param left Is this a left rotation? */ final private void rotate(final AVLMapNode<K,V> child, final AVLMapNode<K,V> node, final AVLMapNode<K,V> parent, boolean left) { if (parent.left == node) { parent.left = child; } else { parent.right = child; } child.parent = parent; node.parent = child; AVLMapNode<K, V> grandChild = left? child.left : child.right; if (left) { node.right = grandChild; if (grandChild != null) { grandChild.parent = node; } child.left = node; node.rightHeight = child.leftHeight; child.leftHeight = Math.max(node.leftHeight, node.rightHeight) + 1; } else { node.left = grandChild; if (grandChild != null) { grandChild.parent = node; } child.right = node; node.leftHeight = child.rightHeight; child.rightHeight = Math.max(node.leftHeight, node.rightHeight) + 1; } } /** * @see java.util.AbstractMap#clear() */ @Override public void clear() { root.parent.lockSuccLock(); root.lockTreeLock(); root.parent.succ = root; root.pred = root.parent; root.left = null; root.leftHeight = 1; root.parent.unlockSuccLock(); root.unlockTreeLock(); } /** * @return The height of the tree */ final public int height() { return height(root.left); } /** * Returns the height of the sub-tree rooted at the given node. * * @param node The given node * @return The height of the sub-tree rooted by node */ final public int height(AVLMapNode<K,V> node) { if (node == null) return 0; int rMax = height(node.right); int lMax = height(node.left); return Math.max(rMax, lMax) + 1; } /** * @see java.util.AbstractMap#size() */ @Override final public int size() { return size(root.left); } /** * Returns the number of nodes in the sub-tree rooted at the given node. * * @param node The given node * @return The number of nodes in the sub-tree rooted at node */ final public int size(AVLMapNode<K,V> node) { if (node == null) return 0; int rMax = size(node.right); int lMax = size(node.left); return rMax+lMax + 1; } /** * The tree is empty if the root's left child is empty * @see java.util.AbstractMap#isEmpty() */ @Override public boolean isEmpty() { return root.left == null; } /** * @see java.util.Map#entrySet() */ @Override public Set<java.util.Map.Entry<K, V>> entrySet() { return new AbstractSet<Map.Entry<K,V>>() { /** * @see java.util.AbstractCollection#size() */ @Override public int size() { return LogicalOrderingAVL.this.size(); } /** * @see java.util.AbstractCollection#isEmpty() */ @Override public boolean isEmpty() { return LogicalOrderingAVL.this.isEmpty(); } /** * @see java.util.AbstractCollection#contains(java.lang.Object) */ @Override public boolean contains(Object o) { K key = (K) ((Entry) o).getKey(); if (((Entry) o).getValue() == null) return false; V v = get(key); if (v == null) return false; return v.equals(((Entry) o).getValue()); } /** * @see java.util.AbstractCollection#add(java.lang.Object) */ @Override public boolean add(java.util.Map.Entry<K, V> e) { return put(e.getKey(), e.getValue()) != e.getValue(); } /** * @see java.util.AbstractCollection#remove(java.lang.Object) */ @Override public boolean remove(Object o) { return LogicalOrderingAVL.this.remove(((Entry) o).getKey(), ((Entry) o).getValue()); } /** * @see java.util.AbstractCollection#iterator() */ @Override public Iterator<java.util.Map.Entry<K, V>> iterator() { return new Iterator<Map.Entry<K,V>>() { private AVLMapNode<K, V> curr = root.parent; private AVLMapNode<K, V> currNext = curr; @Override public boolean hasNext() { getNext(); return currNext != root; } @Override public java.util.Map.Entry<K, V> next() { getNext(); curr = currNext; return curr == root? null : new SimpleImmutableEntry<K, V>(curr.key, (V) curr.item); } private void getNext() { if (currNext == curr) { currNext = curr.succ; while (!currNext.valid) currNext = curr.succ; } } @Override public void remove() { if (curr != root && curr != root.parent) LogicalOrderingAVL.this.remove(curr.key, curr.item); } }; } }; } /** * A tree node * * @author Dana * * @param <K> * @param <V> */ class AVLMapNode<K,V> { /** The node's key. */ public final K key; /** The node's item. */ public volatile Object item; /** Is the node valid? i.e. it was not marked as removed. */ public volatile boolean valid; /** The predecessor of the node (with respect to the ordering layout). */ public volatile AVLMapNode<K, V> pred; /** The successor of the node (with respect to the ordering layout). */ public volatile AVLMapNode<K, V> succ; /** The lock that protects the node's {@code succ} field and the {@code pred} field of the node pointed by {@code succ}. */ final public Lock succLock; /** The parent of the node (with respect to the tree layout). */ public volatile AVLMapNode<K, V> parent; /** The left child of the node (with respect to the tree layout). */ public volatile AVLMapNode<K, V> left; /** The right child of the node (with respect to the tree layout). */ public volatile AVLMapNode<K, V> right; /** The height of the sub-tree rooted at {@code left}. */ public int leftHeight; /** The height of the sub-tree rooted at {@code right}. */ public int rightHeight; /** The lock that protects the node's tree fields, that is, {@code parent, left, right, leftHeight, rightHeight}. */ final public ReentrantLock treeLock; /** * Constructor, create a new node. * * @param key The new node's key * @param item The new node's item * @param pred The new node's predecessor (with respect to the ordering layout) * @param succ The new node's successor (with respect to the ordering layout) * @param parent The new node's parent (with respect to the tree layout) */ public AVLMapNode(final K key, final Object item, final AVLMapNode<K, V> pred, final AVLMapNode<K, V> succ, final AVLMapNode<K, V> parent) { this.key = key; this.item = item; valid = true; this.pred = pred; this.succ = succ; succLock = new ReentrantLock(); this.parent = parent; right = null; left = null; leftHeight = 0; rightHeight = 0; treeLock = new ReentrantLock(); } /** * Constructor, create a new node with the given key. * * @param key The new node's key */ public AVLMapNode(K key) { this(key, null, null, null, null); } /** * Lock the node's {@code treeLock}. */ public void lockTreeLock() { treeLock.lock(); } /** * Attempt to lock the node's {@code treeLock} without blocking. * * @return true if the lock was acquired, and false otherwise */ public boolean tryLockTreeLock() { return treeLock.tryLock(); } /** * Release the node's {@code treeLock}. */ public void unlockTreeLock() { treeLock.unlock(); } /** * Returns the balance factor of the node, that is, the difference * between the heights of the left sub-tree and the right sub-tree. * * @return the node's balance factor */ public int getBalanceFactor() { return leftHeight - rightHeight; } /** * Lock the node's {@code succLock}. */ public void lockSuccLock() { succLock.lock(); } /** * Release the node's {@code succLock}. */ public void unlockSuccLock() { succLock.unlock(); } /** * @see java.lang.Object#toString() */ @Override public String toString() { String delimiter = " "; StringBuilder sb = new StringBuilder(); sb.append("(" + key + delimiter + ", " + valid + ")" + delimiter); return sb.append(" [" + leftHeight + ":" + rightHeight + "]").toString(); } } }