Advanced locking

884 views

Published on

Published in: Education, Business, Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
884
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
0
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • Nodes are sorted, so that it is quick to detect the absence of a node. We will assume the keys are unique.
  • Use a single lock to manage all the synchronization operations.
  • The problem is that there is no overlap between the locks held by the two remove() calls.
  • This is because another thread could remove node b after you release pred but before you acquire curr.
  • Problems with fine-grained locking: Still requires a lot of lock acquisitions and releases. Threads access disjoint items may still block each other, e.g., a thread removing the second item in the list blocks all concurrent threads searching for later nodes.
  • Validation checks that pred is still reachable, and it still points to curr.
  • It is possible that the trail of references leading to pred or the reference from pred to curr could have changed between when they were last read by a thread and when the thread acquires the locks (to perform the actual operation).
    Note that absence of interference implies that once a node has been unlinked from the list, the value of its next field does not change, so following a sequence of such links eventually leads back to the list.
  • OptimisticLock works well if the cost of traversing the list twice without locking is significantly less than the cost of traversing the list once with locking.
    However, one drawback is that contains() are likely to be called very often, and it requires locks.
    Traversal does not require locking.
    Maintains the invariant that every unmarked node is reachable.
    Remove takes two steps: First, mark the target node, logically removing it, and second, redirect its predecessor’s next field, physically removing it.
  • Advanced locking

    1. 1. Advanced Locking Techniques • • • • Coarse-grained Synchronization Fine-grained Synchronization Optimistic Synchronization Lazy Synchronization
    2. 2. List-based Set head pred curr tail c a b head pred a tail curr b c class node { T item; int key; Node next; }
    3. 3. Coarse-grained Synchronization • Take a sequential implementation, add a lock field, and ensure that each method call acquires and releases this lock • Simple, and works well when levels of concurrency is low • When too many threads try to access the object at the same time, the object becomes a sequential bottleneck
    4. 4. Coarse-grained Locking public class CoarseList<T> { private Node head; private Lock lock = new ReentrantLock (); public CoarseList () { head = new Node (Integer.MIN_VALUE); head.next = new Node (Integer.MAX_VALUE); } public boolean add (T item) { Node pred, curr; int key = item.hashCode (); lock.lock (); try { pred = head; curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } if (key == curr.key) { return false; } else { Node node = new Node (item); node.next = curr; pred.next = node; return true; } finally { lock.unlock (); } } } public boolean remove (T item) { Node pred, curr; int key = item.hashCode (); lock.lock (); try { pred = head; curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } } if (key = curr.key) { pred.next = curr.next; return true; } else { return false; } } finally { lock.unlock (); }
    5. 5. Fine-grained Synchronization • Improve concurrency by locking individual nodes, instead of locking the entire list • Operations interfere only when trying to access the same node at the same time • Higher concurrency, but requires a lock to be added for each node
    6. 6. Fine-grained Locking public class CoarseList<T> { private Node head; public CoarseList () { head = new Node (Integer.MIN_VALUE); head.next = new Node (Integer.MAX_VALUE); } public boolean add (T item) { int key = item.hashCode (); head.lock (); Node pred = head; try { Node curr = pred.next; curr.lock (); try { while (curr.key < key) { pred.unlock(); pred = curr; curr = curr.next; curr.lock (); } if (key == curr.key) { return false; } Node newNode = new Node (item); newNode.next = curr; pred.next = newNode; return true; } finally { curr.unlock (); } } finally { pred.unlock (); } } } public boolean remove (T item) { Node pred = null, curr = null; int key = item.hashCode (); head.lock (); try { pred = head; curr = pred.next; curr.lock(); try { while (curr.key < key) { pred.unlock (); pred = curr; curr = curr.next; curr.lock (); } if (key = curr.key) { pred.next = curr.next; return true; } return false; } finally { curr.unlock (); } } finally { pred.unlock (); } }
    7. 7. Why Two Locks? head pred a Thread A Thread B tail curr b c
    8. 8. Hand-Over-Hand Locking head pred a Thread A tail curr b c Thread B Except for the initial head node, acquire the lock for curr only while holding the lock for pred
    9. 9. Optimistic Synchronization • Search a component without acquiring any locks • When a node is found, it locks the component, and then checks whether the component has been changed • Optimistic about the possibility of conflicting
    10. 10. Optimistic Locking (1) public boolean add (T item) { int key = item.hashCode (); while (true) { Node pred = head; Node curr = pred.next; while (curr.key <= key) { pred = curr; curr = curr.next; } pred.lock(); curr.lock (); try { if (validate(pred, curr)) { if (curr.key == key) { return false; } else { Node node = new Node (item); node.next = curr; pred.next = node; return true; } } } finally { pred.unlock (); curr.unlock(); } } } public boolean remove (T item) { int key = item.hashCode (); while (true) { Node pred = head; Node curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } pred.lock(); curr.lock (); try { if (validate(pred, curr)) { if (curr.key == key) { pred.next = curr.next; return true; } else { return flase; } } } finally { pred.unlock (); curr.unlock(); } } }
    11. 11. Optimistic Locking (2) public boolean contains (T item) { int key = item.hashCode (); while (true) { Node pred = head; Node curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } try { pred.lock(); curr.lock (); if (validate(pred, curr)) { return (curr.key = key); } } finally { pred.unlock (); curr.unlock(); } } } public boolean validate ( Node pred, Node curr) { Node node = head; while (node.key <= pred.key) { if (node == pred) return pred.next = curr; node = node.next; } return false; }
    12. 12. Why validation? head pred tail curr
    13. 13. Lazy Synchronization • Postpone significant changes, e.g., physically removing a node from a linked list • Allow a list to be traversed only once without locking, and contains() is wait-free. • Require a new field marked to be added into each node
    14. 14. Lazy Locking (1) public boolean add (T item) { int key = item.hashCode (); while (true) { Node pred = head; Node curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } pred.lock(); try { curr.lock(); try { if (validate(pred, curr)) { if (curr.key == key) { return false; } else { Node node = new Node (item); node.next = curr; pred.next = node; return true; } } } finally { curr.unlock(); } } finally { pred.unlock (); } } } public boolean remove (T item) { int key = item.hashCode (); while (true) { Node pred = head; Node curr = pred.next; while (curr.key < key) { pred = curr; curr = curr.next; } pred.lock(); try { curr.lock (); try { if (validate(pred, curr)) { if (curr.key != key) { return false; } else { curr.marked = true; pred.next = curr.next; return flase; } } } finally { curr.unlock (); } } finally { pred.unlock (); } } }
    15. 15. Lazy Locking (2) public boolean contains (T item) { int key = item.hashCode (); Node curr = head; while (curr.key < key) { curr = curr.next; } return curr.key == key && ! curr.marked; } private boolean validate (Node pred, Node curr) { return !pred.marked && !curr.marked && pred.next = curr; }
    16. 16. Can we do better? • Non-blocking synchronization: eliminate locks entirely, relying on built-in atomic operations such as compareAndSet() for synchronization

    ×