简体   繁体   中英

Problems in MCS Lock Implementation - JAVA

I have written the following code (taken from the book "The Art of Multiprocessor Programming"):

package Chapter7;

import java.util.concurrent.atomic.AtomicReference;

public class MCSLock implements Lock {

    AtomicReference<QNode> tail;
    ThreadLocal<QNode> myNode;

    public MCSLock() {
        tail = new AtomicReference<>(null);
        myNode = new ThreadLocal<QNode>() {
            @Override
            protected QNode initialValue() {
                return new QNode();
            }
        };
    }

    @Override
    @SuppressWarnings("empty-statement")
    public void lock() {
        QNode qnode = myNode.get();
        QNode pred = tail.getAndSet(qnode);
        if (pred != null) {
            qnode.locked = true;
            pred.next = qnode;
            while (qnode.locked);   // line A
        }
    }

    @Override
    @SuppressWarnings("empty-statement")
    public void unlock() {
        QNode qnode = myNode.get();
        if (qnode.next == null) {
            if (tail.compareAndSet(qnode, null)) {
                return;
            }
            while (qnode.next == null);    // line B
        }
        qnode.next.locked = false;
        qnode.next = null;
    }

    class QNode {
        boolean locked = false;
        QNode next = null;
    }
}

This seems to work if I test it with a small number of threads and operations, but it goes in deadlock every time I try to use 8 threads and 1000 operations per thread protected by this Lock. I inserted some prints to debug the code and an other thread that collects data on the working threads. I found that:

  • Sometimes the deadlock is on line A, sometimes on line B.
  • In the first case, all the threads are looping on line A. The other thread that is collecting data, shows that the variables on which the threads are looping on are all true but one, so a thread should have the possibility to make progress!
    • In the second case, all threads but one are looping on line A, the other is looping on line B. The data-collecting thread shows that qnode.next is not null.
    • There are not starving threads since I checked that they are "active waiting" inserting simple counters (and they are increasing).

The test is done on a simple PriorityQueue.

The problem with this code is that it attempts to use ThreadLocal to achieve thread confinement. However, due to linking the QNodes in a linked list and manipulating instances through the next reference and the tail reference, it breaks that thread confinement, and in the absence of other synchronization mechanisms over the QNode fields, visibility of changes is not guaranteed between threads.

Continuing to loop in line A is a consequence of seeing a stale value, despite the qnode.next.locked = false; call in unlock() .

Similarly, continuing to loop in line B is a consequence of seeing a stale value, despite the pred.next = qnode; call in lock() .

In both cases fields of a QNode of another thread are mutated. In the former case qnode.next , and in the latter pred are QNodes of the other thread.

如果在线程 pred.next中运行set pred.next ,然后在不同步的qnode.next线程 qnode.next中检查qnode.next ,则由于内存屏障线程_2可能永远不会获得您在线程_1中设置的值。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM