线程安全的集合

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程安全的集合相关的知识,希望对你有一定的参考价值。

除了.net自带的Concurrent系列的线程安全集合,有时候我们可以有自定义的实现,比如使用锁(lock),但是这使得并发性下降。本文将利用Interlocked类实现线程安全的队列。

首先定义一个辅助类Node,这个节点类将作为队列中的元素。

private class Node
{
    public T Value;
    public Node Next;
    
    public Node(T value)
    {
        Value = value;
    } 
}   

其中,我们定义了节点值属性Value,以及表示下一个节点的引用。

有了这个节点类,我们可以不需要使用.net框架的Queue类就能实现集合。

这个节点类与Haskell的List类型有点相似,比如[x, [x, [x, [x, [x]]]]]。假设给出一个头节点和一个尾节点,这个头节点是[x, [x, [x, [x, [x]]]]],尾节点的Next节点为null,从头节点开始,通过一级一级的引用,就能获取所有的节点值,直到遇到尾节点的Next节点null为止。

为了实现线程安全的队列,我们此时给出想象中的类以及部分字段的样子,如下

public class InterlockedQueue<T>
{
    // other members
    private Node head;
    private Node tail;
    public InterlockedQueue()
    {
        Node node = new Node(default(T));
        head = tail = node;
    }
}           

注意:我们需要把Node类作为InterlockedQueue类的私有类,才能实现参数化类型。

有了存储队列元素的结构之后,剩下的就是考虑如何Enqueue和Dequeue的线程安全操作了,利用前面提到的Interlocked类很容易实现。

Enqueue方法实现

public void Enqueue(T value)
{
    Node node = new Node(value);
    
    while(true)
    {
        Node tail = this.tail;    // Get current tail node
        Node next = tail.Next;     // Get current tail‘s next node
         
        // must be consistent. If not,  re-enter this while loop
        if (object.ReferenceEquals(tail, this.tail))
        {
            // next node of tail must be null, otherwise, other node is inserted, and it needs to re-enter this while loop
            if (object.ReferenceEquals(next, null)
            {
                // begin to insert this node
                if (object.ReferenceEquals(Interlocked.CompareExchange(ref tail.Next, node, next), next)  // (1)
                {
                    // if consistent, execute insert operation and then break this while loop
                    Interlocked.CompareExchanged(ref this.tail, node, tail);
                    break;
                }
            }
            else    // tail was not pointing to last node
                    //  try to swing Tail to the next node
                Interlocked.CompareExchange(ref this.tail, next, tail);   
         }
    }
}

主要思想是将tail的Next节点指向新节点node,然后再讲tail指向node节点。

注意:(1)处的object.ReferenceEquals方法主要是用于判断是否已经交换,如果没有交换,那必然这个if判断为false。

Dequeue方法实现

public bool Dequeue(out T value)
{
    Node head;
    Node tail;
    Node next;

    while (true)
    {
        // read head
        head = this.head;
        tail = this.tail;
        next = head.Next;

        // Are head, tail, and next consistent?
        if (Object.ReferenceEquals(this.head, head))
        {
            // is tail falling behind
            if (Object.ReferenceEquals(head.Next, tail.Next))
            {
                // is the queue empty?
                if (Object.ReferenceEquals(next, null))
                {
                    value = default(T);

                    // queue is empty and cannot dequeue
                    return false;
                }

                Interlocked.CompareExchange<Node>(
                    ref this.tail,
                    next.Next,
                    tail);
            }
            else // No need to deal with tail
            {
                // read value before CAS otherwise another deque might try to free the next node
                value = next.Value;

                // try to swing the head to the next node
                if (Interlocked.CompareExchange<Node>(
                    ref this.head,
                    next,
                    head) == head)
                {
                    return true;
                }
            }
        }
    }
}

 源代码参考网络。

以上是关于线程安全的集合的主要内容,如果未能解决你的问题,请参考以下文章

为啥基于锁的程序不能组成正确的线程安全片段?

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

markdown 线程安全相关片段

Java并发多线程编程——集合类线程不安全之HashMap的示例及解决方案

Java并发多线程编程——集合类线程不安全之HashSet的示例及解决方案

List 集合线程安全测试