• 线程基础知识17 Quene


    1 ConcurrentLinkedQueue

    1.1 简介

      它是一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。
      新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。

      当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择,相对于list,它在多线程环境下是安全的。

      

     

    1.2 构造方法

     

    1.3 常用方法

     

    1.4 isEmpty、size、contains、toArray方法

    private static Queue<String> m1() throws InterruptedException {
            Queue<String> q  = new ConcurrentLinkedQueue<String>();
            System.out.println("队列是否为空" + q.isEmpty());
            System.out.println("队列大小" + q.size());
    
            for (int i = 0; i < 2 ; i++) {
                Thread t = new Thread(() -> {
                    for (int j = 0; j < 10; j++) {
                        q.offer( j+"");
    
                    }
                }, "写线程:" + i);
                t.start();
                t.join();
            }
            System.out.println("队列是否为空" + q.isEmpty());
            System.out.println("队列大小" + q.size());
            System.out.println("是否包含5:" + q.contains("5"));
            Object[] objects = q.toArray();
            for (int i = 0; i < objects.length; i++) {
                System.out.println(objects[i]);
            }
            return q;
        }

    输出

    队列是否为空true
    队列大小0
    队列是否为空false
    队列大小20
    是否包含5:true
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    Process finished with exit code 0

     

    1.5 offer、peek、poll

    private static Queue<String> m2() throws InterruptedException {
            Queue<String> q  = new ConcurrentLinkedQueue<String>();
    
    
            for (int i = 0; i < 2 ; i++) {
                Thread t = new Thread(() -> {
                    for (int j = 0; j < 10; j++) {
                        q.offer(Thread.currentThread().getName() + "  值:" + j);
                        System.out.println(Thread.currentThread().getName() + "添加值" + j);
                    }
                }, "写线程:" + i);
                t.start();
                t.join();
            }
    
            new Thread(()->{
                for (int j = 0; j < 3; j++) {
                    String poll = q.peek();
                    System.out.println("读取头元素值不删除头元素:" + poll);
                }
            }).start();
    
    
            for (int i = 0; i < 2 ; i++) {
                new Thread(()->{
                    for (int j = 0; j < 10; j++) {
                        String poll = q.poll();
                        System.out.println("读取头元素值并删除头元素:" + poll);
                    }
                },"读线程:" + i).start();
            }
            return q;
        }

    输出

    写线程:0添加值0
    写线程:0添加值1
    写线程:0添加值2
    写线程:0添加值3
    写线程:0添加值4
    写线程:0添加值5
    写线程:0添加值6
    写线程:0添加值7
    写线程:0添加值8
    写线程:0添加值9
    写线程:1添加值0
    写线程:1添加值1
    写线程:1添加值2
    写线程:1添加值3
    写线程:1添加值4
    写线程:1添加值5
    写线程:1添加值6
    写线程:1添加值7
    写线程:1添加值8
    写线程:1添加值9
    读取头元素值不删除头元素:写线程:0  值:0
    读取头元素值不删除头元素:写线程:0  值:0
    读取头元素值不删除头元素:写线程:0  值:0
    读取头元素值并删除头元素:写线程:0  值:0
    读取头元素值并删除头元素:写线程:0  值:2
    读取头元素值并删除头元素:写线程:0  值:3
    读取头元素值并删除头元素:写线程:0  值:4
    读取头元素值并删除头元素:写线程:0  值:1
    读取头元素值并删除头元素:写线程:0  值:6
    读取头元素值并删除头元素:写线程:0  值:7
    读取头元素值并删除头元素:写线程:0  值:8
    读取头元素值并删除头元素:写线程:0  值:9
    读取头元素值并删除头元素:写线程:1  值:0
    读取头元素值并删除头元素:写线程:1  值:1
    读取头元素值并删除头元素:写线程:1  值:2
    读取头元素值并删除头元素:写线程:1  值:3
    读取头元素值并删除头元素:写线程:1  值:4
    读取头元素值并删除头元素:写线程:0  值:5
    读取头元素值并删除头元素:写线程:1  值:5
    读取头元素值并删除头元素:写线程:1  值:6
    读取头元素值并删除头元素:写线程:1  值:7
    读取头元素值并删除头元素:写线程:1  值:8
    读取头元素值并删除头元素:写线程:1  值:9
    
    Process finished with exit code 0

     

    1.6 注意事项

      如果此队列包含的元素数大于 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE。
      与大多数 collection 不同,size方法不是 一个固定时间操作。由于这些队列的异步特性,确定当前的元素数需要进行一次花费 O(n) 时间的遍历。
      所以在需要判断队列是否为空时,尽量不要用 queue.size()>0,而是用 !queue.isEmpty()

      此队列不允许使用 null 元素

     

    2 PriorityQueue

    2.1 简介

      它通过二叉小顶堆实现,可以用一棵完全二叉树表示,形成一个有序队列。

     

     

    2.2 示例

    public static void main(String[] args) {
            PriorityQueue q = new PriorityQueue();
    
            q.offer("a");
            q.offer("c");
            q.offer("f");
            q.offer("e");
            q.offer("d");
    
            for (int i = 0; i < 5; i++) {
                System.out.println(q.poll());
            }
        }

    输出

    a
    c
    d
    e
    f

     

    3 LinkedBlockingQueue

    3.1 简介 

      LinkedBlockingQueue是一个线程安全的阻塞队列,实现了先进先出等特性,是作为生产者消费者的首选,可以指定容量,也可以不指定,不指定的话默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法将一个对象放到队列尾部,在队列满的时候会阻塞直到有队列成员被消费才会添加进去,take方法从head取一个对象,在队列为空的时候会阻塞,直到有队列成员被放进来才会取出

     

    3.2 构造函数

    LinkedBlockingQueue() {}
    LinkedBlockingQueue(int capacity)
    LinkedBlockingQueue(Collection<? extends E> c)

     

    3.3 常用方法

    add(anObject):
    把anObject添加到BlockingQueue里,添加成功返回true,如果BlockingQueue空间已满则抛出异常。
    offer(anObject):
    表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false。
    put(anObject):
    把anObject加到BlockingQueue里,如果BlockingQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里有空间再继续。
    poll(time):
    获取并移除此队列的头,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
    take():
    获取BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的对象被加入为止。
    clear():
    从队列彻底移除所有元素。
    remove()方法直接删除队头的元素
    peek()方法直接取出队头的元素,并不删除
    

     

    3.4 示例

    public class DelayQueneTe {
    
    
        static class Ta implements Delayed{
    
            private String name;
    
            private long time;
    
            public Ta(String name, long time) {
                this.name = name;
                this.time = time;
            }
    
            @Override
            public long getDelay(TimeUnit unit) {
                return unit.convert(time - System.currentTimeMillis(),unit); //获取延迟时间
            }
    
            @Override
            public int compareTo(Delayed o) {
                if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)){
                    return -1;
                }else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)){
                    return 1;
                }else{
                    return 0;
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            long now = System.currentTimeMillis();
            BlockingQueue<Ta> q = new DelayQueue<>();
            q.offer(new Ta("t1",now + 1000));
            q.offer(new Ta("t2",now + 3000));
            q.offer(new Ta("t3",now + 2000));
            q.offer(new Ta("t4",now + 5000));
            q.offer(new Ta("t5",now + 4000));
            for (int i = 0; i < 6; i++) {
                System.out.println(q.take().name);
            }
    
            System.out.println("执行完成");
    
    
        }
    }

    输出

    线程0拿到了0
    线程1拿到了1
    线程2拿到了2
    线程3拿到了3
    线程4拿到了4
    线程5拿到了5
    线程6拿到了6
    线程7拿到了7
    线程8拿到了8
    线程9拿到了9
    睡眠结束
    线程10拿到了i
    线程11拿到了j

    先在队列里加入了10个元素,12个线程去取,发现10个线程被取出后,还有两个线程取不到值二阻塞,知道又加入了两个元素

     

    4 DelayQueue

    4.1 简介

      DelayQueue 是一个实现延迟获取元素的无界阻塞的队列,它是BlockingQuene的一种,它是线程安全的。

      其中添加进该队列的元素必须实现Delayed接口(指定延迟时间),而且只有在延迟期满后才能从中提取元素

      可以用作定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行

     

    4.2 构造方法

        //从上面属性就可以看出,DelayQueue采用了饿汉模式,调用构造方法即创建了队列实例
        public DelayQueue() {}
    
        /**
         * 创建一个DelayQueue,最初包含给定的Collection实例集合。
         * @param c 最初包含的元素集合
         */
        public DelayQueue(Collection<? extends E> c) {
            this.addAll(c);
        }

     

    4.3 put和take

      offer:添加元素

      take:获取元素

     

    4.4 DelayQuene的元素

      使用DelayQueue的话,放入该队列的元素必须实现Delayed接口,实现的接口中有两个参数:延迟时间单位,优先级规则,take方法会根据规则按照优先级执行

     

    4.5 示例

    public class DelayQueneTe {
    
    
        static class Ta implements Delayed{
    
            private String name;
    
            long delayTime; // 延迟时间
            long expire; // 过期时间
    
            public Ta(String name, long delayTime) {
                this.name = name;
                this.delayTime = delayTime;
                this.expire = System.currentTimeMillis() + delayTime;
            }
    
            //剩余时间 = 到期时间 - 当前时间
            @Override
            public long getDelay(TimeUnit unit) {
                return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
            }
    
            //优先级规则:两个任务比较,时间短的优先执行。一个任务到时间了,被取出,就没有优先级的说法,多个任务到执行时间了,没有被取出,现在取出,按这个优先级别取出
            @Override
            public int compareTo(Delayed o) {
                long f = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
                return (int) f;
            }
        }
        public static void main(String[] args) throws InterruptedException {
            long now = System.currentTimeMillis();
            BlockingQueue<Ta> q = new DelayQueue<>();
            q.offer(new Ta("t1",  1000));
            q.offer(new Ta("t2", 3000));
            q.offer(new Ta("t3", 2000));
            q.offer(new Ta("t4", 5000));
            q.offer(new Ta("t5", 4000));
            for (int i = 0; i < 6; i++) {
                System.out.println(q.take().name);
            }
    
            System.out.println("执行完成");
    
    
        }
    }

    执行结果

    t1
    t3
    t2
    t5
    t4

    发现,它是按照传入的时间来执行的

    且由于只有5个元素添加了,所以取第六个的时候,一致取不到,就会一直阻塞

     

    5 SynchronousQueue 

    5.1 简介

      SynchronousQueue是BlockingQueue的一种,所以SynchronousQueue是线程安全的。

      SynchronousQueue 是比较独特的队列,其本身是没有容量大小,比如我放一个数据到队列中,我是不能够立马返回的,我必须等待别人把我放进去的数据消费掉了,才能够返回。

      也就是说SynchronousQueue的每一次insert操作,必须等待其他线性的remove操作。而每一个remove操作也必须等待其他线程的insert操作。

      这种特性和Exchanger有点类似。
      SynchronousQueue 在消息队列技术中间件中被大量使用

      

    5.2 示例

    5.2.1 一个线程取数据,一个线程放数据,实现了线程间的数据传递

    private static void m1() throws InterruptedException {
            BlockingQueue queue = new SynchronousQueue();
    
            new Thread(()->{
                try {
                    Object take = queue.take();
                    System.out.println(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(()->{
                try {
                    queue.put("aaa");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
    
         

    输出

    aaa

    5.2.2 SynchronousQueue的每一次添加操作,必须等待其他线性的取出操作,不然会阻塞

    private static void m2() throws InterruptedException {
            BlockingQueue queue = new SynchronousQueue();
    
            new Thread(()->{
                try {
                    queue.put("aaa"); //若没有人取,会一直阻塞。
                    System.out.println("xxxx");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    没有输出,阻塞了

    5.2.3 SynchronousQueue的每一次取操作,必须等待其他线性的添加操作,不然会阻塞

    private static void m3() throws InterruptedException {
            BlockingQueue queue = new SynchronousQueue();
    
    
            new Thread(()->{
                try {
                    Object take = queue.take();
                    System.out.println(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
        }

    没有输出,阻塞了

    5.2.4 多个线程先取,多个线程再添加

    private static void m4() throws InterruptedException {
            BlockingQueue queue = new SynchronousQueue();
            new Thread(()->{
                try {
                    Object take = queue.take();
                    System.out.println(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(()->{
                try {
                    Object take = queue.take();
                    System.out.println(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(()->{
                try {
                    queue.put("aaa");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(()->{
                try {
                    queue.put("bbb");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
    
        }

    输出

    aaa
    bbb

    5.2.5 多个线程先添加,多个线程再取

     private static void m6() throws InterruptedException {
            BlockingQueue queue = new SynchronousQueue();
    
            new Thread(()->{
                try {
                    queue.put("aaa");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(()->{
                try {
                    queue.put("bbb");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
        
    
            new Thread(()->{
                try {
                    Object take = queue.take();
                    System.out.println(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(()->{
                try {
                    Object take = queue.take();
                    System.out.println(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
        }

    输出

    bbb
    aaa

    5.2.6 add方法(调用会报错)

    private static void m8() throws InterruptedException {
            BlockingQueue queue = new SynchronousQueue();
    
            new Thread(()->{
                queue.add("aaa");
                System.out.println("添加aaa成功");
    
            }).start();
        }

    输出报错

    Exception in thread "Thread-0" java.lang.IllegalStateException: Queue full
        at java.util.AbstractQueue.add(AbstractQueue.java:98)
        at com.ruoyi.weixin.user.MyTest.Quene.SynchrnousQueneTe.lambda$m8$16(SynchrnousQueneTe.java:211)
        at java.lang.Thread.run(Thread.java:748)

    6 LinkedTransferQueue

    6.1 简介

      LinkedTransferQueue是BlockingQueue的一种,所以SynchronousQueue是线程安全的。

      LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。可以算是 LinkedBolckingQueue 和 SynchronousQueue 和合体。LinkedTransferQueue是一种无界阻塞队列,底层基于单链表实现,其内部节点分为数据结点、请求结点;基于CAS无锁算法实现。

      它的添加方法提供阻塞的transfer方法,取出方法提供阻塞的take方法

      

     

    6.2 原理

    6.2.1 消费者

      LinkedTransferQueue 消费者线程获取取数据时:调用take poll 等方法。

      如果队列不为空,则直接取走数据,若队列为空则消费者线程会生成一个占位虚拟节点(节点元素为null)入队,并等待在这个节点上,后面生产者线程请求添加数据时,会从单向链表的head节点开始遍历,如果发现某个节点是一个取数请求任务类型的节点(即是这个节点的isData为false,item == null),生产者线程就不入队了,直接就将元素填充到该节点(元素传递给它),并唤醒该节点等待的消费者线程,被唤醒的消费者线程取走元素 ;

     6.2.2 生产者

      LinkedTransferQueue 生产者线程传递数据时:调用transfer方法

      当有消费者线程阻塞等待时,调用transfer方法的生产者线程不会将元素存入队列,而是直接将元素传递给消费者,并唤醒阻塞的线程;
      如果调用transfer方法的生产者线程发现没有正在等待的消费者线程,则这个生产者请求创建一个节点,这个节点将会被添加到当前链表的末尾将数据入队,然后会阻塞等待,直到有一个消费者线程来获取该元素。

      LinkedTransferQueue内部链表上的有效节点,要么全部都是由取数请求创建的节点,其isData为false,item属性为null;要么就全部都是由存储请求创建的节点,其isData为true,item属性不为null ,只需要由head开始找到第一个有效节点判定是否可以存储/添加数据,因为只要存在生产者或者消费者在队列时,对应的消费者或者生产者就不会入队列,也就是说二者只有一个会在队列,如果生产者在队列,消费者来取数据就会唤醒它,反之消费者在队列,生产者也会唤醒消费者线程

    6.3 示例

    6.3.1  一个线程取数据,一个线程放数据,实现了线程间的数据传递

    private static void m1() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
    
            new Thread(()->{
                Object take = null;
                try {
                    take = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(take);
            }).start();
    
            new Thread(()->{
                try {
                    queue.transfer("aaa");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    输出

    aaa

    6.3.2 每一次添加操作,必须等待其他线性的取出操作,不然会阻塞

    private static void m2() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
    
            new Thread(()->{
                try {
                    queue.transfer("aaa");
                    System.out.println("xxxxx");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    没有输出,阻塞了

    6.3.3 每一次取操作,必须等待其他线性的添加操作,不然会阻塞

    private static void m3() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
    
            new Thread(()->{
                try {
                    Object take = queue.take();
                    System.out.println(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
        }

    没有输出,阻塞了

    6.3.4 多个线程先取,多个线程再添加

    private static void m4() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
    
    
            new Thread(()->{
                Object take = null;
                try {
                    take = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(take);
            }).start();
    
            new Thread(()->{
                Object take = null;
                try {
                    take = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(take);
            }).start();
    
            new Thread(()->{
    
                try {
                    queue.transfer("aaa");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
            new Thread(()->{
    
                try {
                    queue.transfer("bbb");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
        }

    输出

    aaa
    bbb

    6.3.5 多个线程先添加,多个线程再取

    private static void m5() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
            
            new Thread(()->{
    
                try {
                    queue.transfer("aaa");  //阻塞,等待获取
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
            new Thread(()->{
    
                try {
                    queue.transfer("bbb");  //阻塞等待获取
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(()->{
                Object take = null;
                try {
                    take = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(take);
            }).start();
    
            new Thread(()->{
                Object take = null;
                try {
                    take = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(take);
            }).start();
    
    
        }

    输出

    bbb
    aaa

    6.3.6 add方法(非阻塞形式,放完就跑)

    private static void m6() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
    
            new Thread(()->{
                queue.add("aaa");
                System.out.println("添加aaa成功");
    
            }).start();
        }

    输出

    添加aaa成功

    6.3.7  当一个线程transfer阻塞时,其它线程还可以通过add添加

    private static void m7() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
    
            new Thread(()->{
                try {
                    queue.transfer("aaa");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("添加aaa成功");
    
            }).start();
    
    
            new Thread(()->{
                queue.add("bbb");
                System.out.println("添加bbb成功");
    
            }).start();
        }

    输出

    添加bbb成功

    6.3.8 tryTransfer尝试添加,不会阻塞,没有消费者等待时,添加失败,返回false

    private static void m9() throws InterruptedException {
            LinkedTransferQueue queue = new LinkedTransferQueue();
    
            new Thread(()->{
                try {
                    boolean aaa = queue.tryTransfer("aaa");
                    if(aaa){
                        System.out.println("添加aaa成功");
                    }else{
                        System.out.println("添加aaa失败");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
    
            }).start();
            
        }

    输出

    添加aaa失败

    6.4 LinkedTransferQueue 和 synchronousqueue 的区别

      LinkedTransferQueue 如果有消费者线程存在,则生产者线程将数据传递到占位节点并唤醒消费者线程。没有消费者线程存在,则阻塞直到消费者取元素,而其他方法不阻塞,

      synchronousqueue 只有阻塞直到消费者获取元素。这个过程中队列不存数据,直接等到消费者来获取时交给了消费者

  • 相关阅读:
    ASP.NET MVC5(二):控制器、视图与模型
    LINUX重启MYSQL的命令
    mysql unrecognized service问题解决
    UML类图几种关系的总结
    java用org.apache.poi包操作excel
    struts2.xml 中result type属性说明
    MySql Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' 解决方法
    报错:1130-host ... is not allowed to connect to this MySql server 开放mysql远程连接 不使用localhost
    linux下使用yum安装mysql
    关于LINUX权限-bash: ./startup.sh: Permission denied
  • 原文地址:https://www.cnblogs.com/jthr/p/16722968.html
Copyright © 2020-2023  润新知