手写一个线程安全的非阻塞队列,并且实现入队出队方法

本文介绍了一种使用单向链表实现的线程安全队列,通过CAS操作和乐观锁确保多线程环境下队列操作的安全性。详细展示了如何在入队和出队操作中利用CAS进行节点更新,并通过测试验证了其线程安全性。

实现思路:利用单向链表来保存队列的数据,在往队列中添加元素的时候,新建一个节点,加入到队尾,加入到队尾的操作,利用CAS原理加乐观锁,另外,还需要将新加的节点设置为新的队尾,此步操作也需要利用CAS,head与tail变量用volatile修饰,保证多线程环境下的线程可见性。
相关代码如下:

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class MyLinkQueue<E> {

    //头节点
    private volatile Node<E> head;
    //尾节点
    private volatile Node<E> tail;
    //引入Unsafe类
    private static final sun.misc.Unsafe UNSAFE;
    //头节点的内存偏移地址
    private static final long headOffset;
    //尾节点的内存偏移地址
    private static final long tailOffset;

    public MyLinkQueue() {
        head = tail = new Node<E>(null);
    }

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            UNSAFE = (Unsafe) f.get(null);
            Class<?> k = MyLinkQueue.class;
            headOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("head"));
            tailOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("tail"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    /**
     * 内部节点类
     *
     * @param <E>
     */
    private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
        private static final sun.misc.Unsafe UNSAFE;
        private static final long itemOffset;
        private static final long nextOffset;

        //构造方法
        public Node(E item) {
            this.item = item;
        }

        static {
            try {
                Field f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                UNSAFE = (Unsafe) f.get(null);
                Class<?> k = Node.class;
                itemOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("item"));
                nextOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("next"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }

        public boolean casNext(Node<E> o, Node<E> newNode) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, o, newNode);
        }

        public boolean casHeadItem(Object o, E item) {
            return UNSAFE.compareAndSwapObject(this,itemOffset,o,item);
        }
    }

    /**
     * 入队方法
     */
    public boolean offer(E item) {
        //入队元素不能为空
        if (null == item) {
            throw new NullPointerException();
        }
        //创建一个新节点
        Node<E> newNode = new Node<E>(item);
        for (; ; ) {
            //头节点存储的内容为空,表示刚创建的队列,利用cas设置头节点保存item
            if(null==head.item){
                if(head.casHeadItem(null,item)){
                    return true;
                }
            }
            Node<E> q = tail.next;
            //如果尾节点的下一个节点为空,利用cas设置新节点为尾节点的下一个节点
            if (null == q) {
                if (tail.casNext(null, newNode)) {
                    return true;
                }
            }
            //尾节点的下一个节点不为空,设置q为新的尾节点
            else {
                casTail(tail, q);
            }
        }
    }

    /**
     * 出队方法
     *(队列先进先出,取head保存的元素)
     * @return
     */
    public E poll() {
        for(;;){
            //取出头节点
            Node<E> q = head;
            //取出头节点的下一个节点
            Node<E> result = q.next;
            E obj = q.item;
            if(null!=result){
                //将reuslt节点利用cas原理设置为新的头节点
                if(casHead(q,result)){
                    return obj;
                }
            }else{
                //头节点的下一个节点为空,此时head=tail,如果head的item为空,直接返回null,否则应该利用cas设置为null
                if(null==obj){
                    return obj;
                }
                if(head.casHeadItem(obj,null)){
                    return obj;
                }
            }
        }
    }

    private boolean casHead(Node<E> q, Node<E> result) {
        return UNSAFE.compareAndSwapObject(this,headOffset,q,result);
    }

    private void casTail(Node<E> p, Node<E> q) {
        UNSAFE.compareAndSwapObject(this, tailOffset, p, q);
    }

    /**
     * 统计队列大小的方法
     *
     * @return
     */
    public int size() {
        int count = 0;
        Node<E> start = head;
        if(null==head.item){
            return 0;
        }
        for (; ; ) {
            if (null == start) {
                return count;
            }
            ++count;
            start = start.next;
        }

    }
}

相关测试代码如下,测试方法是开启100个线程,每个线程内部循环10次往队列中添加元素,经过测试,最后队列的大小为1000,没有出现线程安全问题。然后开启100个线程,每个线程循环10次从队列中取数据,最后队列的大小为0,也是线程安全的


 public class TestMyLinkQueue {
    public static void main(String[] args) {

        final MyLinkQueue<Integer> myLinkQueue = new MyLinkQueue<Integer>();

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        myLinkQueue.offer(i);
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("自定义线程安全队列测试结果(1000个并发数据入队),队列大小为:" + myLinkQueue.size());
        for (int i = 0; i < 100; i++) {
            new Thread(
                    new Runnable() {
                        public void run() {
                            for(int i = 0;i<10;i++){
                                myLinkQueue.poll();
                            }
                        }
                    }
            ).start();
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("自定义线程安全队列测试结果(1000个并发数据出队),队列大小为:" + myLinkQueue.size());
        System.out.println("自定义线程安全队列测试结果,空队列出队数据为:"+myLinkQueue.poll());
    }
}

相关测试结果如下所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200309104743606.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1dLemhhbmdsaWFuZzEyMw==,size_16,color_FFFFFF,t_70)
### 用数组实现队列的基本原理 队列是一种先进先出(FIFO)的线性数据结构,常用于任务调度、缓冲区管理等场景。使用数组实现队列的核心在于模拟队列的两个基本操作:**入队(enqueue)** 和 **出队(dequeue)**。通过维护一个数组和两个指针(队头指针 `front` 和队尾指针 `rear`),可以高效地管理队列中的元素。 在 Java 中,虽然有内置的 `Queue` 接口及其实现类(如 `LinkedList` 和 `ArrayDeque`),但理解如何手动实现队列有助于更好地掌握数据结构的底层机制。 ### 队列的数组实现步骤 1. **定义数组和指针**: - 使用一个数组 `int[] arr` 存储队列元素。 - 定义两个指针变量 `front`(指向队列头部)和 `rear`(指向队列尾部)。 - 定义队列的容量 `capacity`,并初始化数组大小。 2. **入队操作(enqueue)**: - 检查队列是否已满(`rear == capacity`)。 - 如果未满,则在 `rear` 指针位置插入元素,并将 `rear` 向后移动一位。 3. **出队操作(dequeue)**: - 检查队列是否为空(`front == rear`)。 - 如果非空,取出 `front` 指针位置的元素,并将 `front` 向后移动一位。 4. **判断队列状态**: - 队列为空:`front == rear`。 - 队列为满:`rear == capacity`。 以下是一个基于数组实现队列的示例代码: ```java public class MyArrayQueue { private int[] arr; private int front; // 队列头部指针 private int rear; // 队列尾部指针 private int capacity; // 队列容量 public MyArrayQueue(int size) { this.capacity = size; this.arr = new int[capacity]; this.front = 0; this.rear = 0; } // 入队操作 public void enqueue(int value) { if (rear == capacity) { System.out.println("队列已满,无法入队"); return; } arr[rear++] = value; } // 出队操作 public int dequeue() { if (front == rear) { throw new RuntimeException("队列为空"); } return arr[front++]; } // 判断队列是否为空 public boolean isEmpty() { return front == rear; } // 获取队列当前长度 public int size() { return rear - front; } } ``` ### 优化建议 - **循环队列**:上述实现存在“假溢出”问题,即当 `rear` 到达数组末尾时,即使数组前面有空位也无法继续入队。可以通过实现**循环队列**来解决此问题,即将数组视为首尾相连的结构。 - **动态扩容**:如果队列容量不足,可以在入队时动态扩展数组大小,提升灵活性[^1]。 ### 示例用法 ```java public class TestMyQueue { public static void main(String[] args) { MyArrayQueue queue = new MyArrayQueue(5); queue.enqueue(10); queue.enqueue(20); queue.enqueue(30); System.out.println("出队元素:" + queue.dequeue()); // 输出 10 System.out.println("队列是否为空:" + queue.isEmpty()); // 输出 false System.out.println("队列当前大小:" + queue.size()); // 输出 2 } } ``` ### 总结 通过数组实现队列,可以清晰地理解队列的先进先出特性,并掌握基本的指针操作和数组管理技巧。上述实现虽然简单,但为后续学习更复杂的队列结构(如循环队列、优先队列)打下坚实基础[^2]。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值