Download presentation
1
Advanced Locking Techniques
Coarse-grained Synchronization Fine-grained Synchronization Optimistic Synchronization Lazy Synchronization
2
List-based Set head pred curr tail a b c head pred curr tail a b c
Nodes are sorted, so that it is quick to detect the absence of a node. We will assume the keys are unique. class node { T item; int key; Node next; }
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 Use a single lock to manage all the synchronization operations.
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
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
Fine-grained Locking public boolean remove (T item) {
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 (); while (curr.key < key) { pred.unlock(); pred = curr; curr = curr.next; if (key == curr.key) { return false; Node newNode = new Node (item); newNode.next = curr; pred.next = newNode; return true; } finally { curr.unlock (); 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(); 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 ();
7
Why Two Locks? head pred curr tail a b c Thread A Thread B
The problem is that there is no overlap between the locks held by the two remove() calls. Thread A Thread B
8
Hand-Over-Hand Locking
head pred curr tail a b c This is because another thread could remove node b after you release pred but before you acquire curr. Thread A Thread B Except for the initial head node, acquire the lock for curr only while holding the lock for pred
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 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.
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
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; Validation checks that pred is still reachable, and it still points to curr.
12
Why validation? head pred tail 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.
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 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.
14
Lazy Locking (1) public boolean remove (T item) {
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(); 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(); 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 (); if (validate(pred, curr)) { if (curr.key != key) { return false; } else { curr.marked = true; pred.next = curr.next; return flase; } finally { curr.unlock (); pred.unlock ();
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
Can we do better? Non-blocking synchronization: eliminate locks entirely, relying on built-in atomic operations such as compareAndSet() for synchronization
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.