• Java并发编程之阻塞队列


    Java并发编程之阻塞队列

    实现线程安全的队伍有2种方式:

    1. 阻塞式的, 也就是加锁
    2. 非阻塞式的, 使用CAS, ConcurrentLinkedQueue就是使用的这种方式

    阻塞队列提供两个附加的操作, 阻塞添加和阻塞移除:

    • 阻塞添加: 当队列满时, 队列会阻塞添加元素的线程, 直到队列不满.
    • 阻塞移除: 当队列空时, 队列会阻塞移除元素的线程, 直到队列不空.
    操作/处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
    入队方法 add(e) offer(e) put(e) offer(e, time, unit)
    出队方法 remove() poll() take() poll(time, unit)
    检查方法 element() peek()

    offer()方法返回值类型为boolean, 入队失败返回false;
    poll()方法若出队失败, 则返回null;
    peek()方法只获取元素, 并不执行出队, 并获取不到元素时返回null;

    使用场景: 生产者/消费者, 生产者向队列中添加元素, 消费者从队列中获取元素.

    本文介绍的是Java中的7个阻塞式的队列.

    ArrayBlockingQueue

    ArrayBlockingQueue底层实现为数据, 是一个有界的阻塞队列, 此队列以FIFO的原则对元素进行排序.

    public class TestArrayBlockingQueue {
    
        // 创建一个容量为5的ArrayBlockingQueue
        static BlockingQueue<String> queue = new ArrayBlockingQueue<String>(5);
        
        public static void main(String[] args) throws Exception {
            // 启动10个生产者线程, 每个生产者向queue中put自己的线程名称
            // 并且put之后打印自己的名称
            for(int i=0; i<10; i++) {
                new Thread(new Runnable(){
                    public void run() {
                        try {
                            queue.put(Thread.currentThread().getName());
                            System.out.println(Thread.currentThread().getName() + " put");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, "producer-"+i).start();
            }
            Thread.sleep(5000); // 睡5秒, 让10个生产者都开始执行
            // 打印队列的实际长度, 为5, 
            // 因为没有被消费, 所以只有5个生产者线程put成功, 
            // 其他线程被阻塞
            System.out.println("queue size: " + queue.size());
            // 创建10个消费者线程, 每个线程去queue中做一次take操作
            // 每take一个元素, 就会有一个生产者线程被唤醒, 向queue中put元素
            for(int i=0; i<10; i++) {
                new Thread(new Runnable(){
                    public void run() {
                        try {
                            queue.take();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, "concumer-"+i).start();
            }
            Thread.sleep(5000);
            System.out.println("queue size: " + queue.size());
        }
    }
    

    ArrayBlockingQueue默认是非公平队列;
    公平队列是指被阻塞的线程, 可以按照被阻塞的顺序访问队列, 即先被阻塞的线程先访问队列. 非公平队列是指被阻塞的队列, 在队列可以用时, 都有同等的资格来争夺资源. 创建公平队列可以借助ArrayBlockingQueue的另一个构造方法:

    BlockingQueue<String> queue = new ArrayBlockingQueue<String>(5, true);
    

    LinkedBlockingQueue

    LinkedBlockingQueue的底层实现为链表, 与ArrayBlockingQueue类似, 是一个有界的阻塞队列; 不同的是LinkedBlockingQueue可以不指定队列长度, 此时长度为Integer.MAX_VALUE; LinkedBlockingQueue也是以FIFO原则对元素进行排序.

    PriorityBlockingQueue

    PriorityBlockingQueue从命名上可以看出, 它是支持优先级排序的阻塞队列.
    PriorityBlockingQueue的底层实现为数据, 是一个无界的阻塞队列. 它并不阻塞生产者线程, 只阻塞消费者线程, 所以当生产者的生产速度快于消费者的消费速度时, 随着时间推移, 会最终消耗所有可用内存.

    PriorityBlockingQueue支持通过自定义compareTo()方法进行排序, 也可以通过指定Comparator进行排序.

    public class TestPriorityBlockingQueue {
    
        static BlockingQueue<RegisterInfo> queue = new PriorityBlockingQueue<RegisterInfo>();
        
        private static class RegisterInfo implements Comparable<RegisterInfo> {
            
            private String name;
            
            private int priority;
            
            public RegisterInfo(String name, int priority) {
                this.name = name;
                this.priority = priority;
            }
    
            @Override
            public int compareTo(RegisterInfo registerInfo) {
                if(this.priority > registerInfo.getPriority()) {
                    return 1;
                } else if (this.priority < registerInfo.getPriority()){
                    return -1;
                } else {
                    return 0;
                }
            }
    
            public String getName() {
                return name;
            }
            
            public int getPriority() {
                return priority;
            }
            
        }
        
        public static void main(String[] args) throws Exception {
            RegisterInfo info1 = new RegisterInfo("张三", 5);
            RegisterInfo info2 = new RegisterInfo("李四", 7);
            RegisterInfo info3 = new RegisterInfo("王五", 3);
            queue.add(info1);
            queue.add(info2);
            queue.add(info3);
            System.out.println(queue.take().getName());
            System.out.println(queue.take().getName());
            System.out.println(queue.take().getName());
        }
    }
    

    DelayQueue

    DelayQueue是一个支持可以延时获取元素的无界队列, 内部使用PriorityQueue实现. 放入DelayQueue队列中的元素必须实现Delayed接口, 创建元素时指定延时多长时间才能从队列中获取到此元素.

    DelayQueue可以用于缓存的设计, 使用DelayQueue保存缓存的有效时间, 使用一个线程循环队列, 能从队列中取出元素时, 表示缓存的有效时间到了.

    public class TestDelayQueue {
    
        static BlockingQueue<CacheTime> queue = new DelayQueue<CacheTime>();
        
        private static class CacheTime implements Delayed {
            
            private String id;
            
            private long time;
            
            public CacheTime(String id, long time) {
                this.id = id;
                this.time = time;
            }
    
            @Override
            public int compareTo(Delayed delayed) {
                long diff = 0L;
                if(delayed instanceof CacheTime) {
                    CacheTime other = (CacheTime) delayed;
                    diff = this.time - other.time;
                } else {
                    diff = this.time - delayed.getDelay(TimeUnit.MILLISECONDS);
                }
                if(diff > 0) {
                    return 1;
                } else if(diff < 0) {
                    return -1;
                } else {
                    return 0;
                }
            }
    
            @Override
            public long getDelay(TimeUnit unit) {
                return time - System.currentTimeMillis();
            }
            
            public String getId() {
                return this.id;
            }
        }
        
        public static void main(String[] args) throws Exception {
            long now = System.currentTimeMillis();
            CacheTime ct1 = new CacheTime("001", now + 3000);
            CacheTime ct2 = new CacheTime("002", now + 5000);
            CacheTime ct3 = new CacheTime("003", now + 1000);
            queue.add(ct1);
            queue.add(ct2);
            queue.add(ct3);
            for(int i=0; i<3; i++) {
                System.out.println(queue.take().getId());
            }
        }
    }
    

    CacheTime类用于模拟缓存有效时间, id为缓存id, time为缓存有效时间.
    CacheTime实现了Delayed接口:
    getDelay()方法用于返回此元素剩余的延时时间
    compareTo()方法用于排序

    在main()方法, 创建了3个CacheTime对象, 并设定其延时时间为3秒后, 5秒后, 1秒后; 将其加入到DelayQueue中, 循环DelayQueue, 元素将根据延时时间由少到多依次延时出队.

    SynchronousQueue

    SynchronousQueue是一个不存元素的阻塞队列, 它的容量为0. 每次执行put操作必须等待一个take操作.
    SynchronousQueue支持公平锁. 其内部分别使用TransferQueue和TransferStack保存被阻塞线程的引用.

    这里的TransferQueue是SynchronousQueue的内部类, 不是那继承了BlockingQueue的TransferQueue接口.

    public class TestSynchronousQueue {
    
        static BlockingQueue<String> queue = new SynchronousQueue<String>();
        
        public static void main(String[] args) throws Exception {
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        queue.put("hello");
                        System.out.println("put complete...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "producer").start();
            Thread.sleep(1000);
            System.out.println("queue size: " + queue.size());
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        System.out.println("take: " + queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "consumer").start();
        }
    }
    

    示例中, producer线程put之后将一直阻塞, 直到consumer线程执行take操作.
    但是, 由于add方法非阻塞, 所以, 如果没有consumer线程等待消费数据, add方法则抛出异常.

    SynchronousQueue的一个使用场景就是在线程池中. Executors.newCachedThreadPool()内部就是使用了SynchronousQueue.

    LinkedTransferQueue

    LinkedTransferQueue是一个用链表实现的无界阻塞队列.
    LinkedTransferQueue实现了TransferQueue接口, 扩展了transfer和tryTransfer两个方法.

    • transfer(): 生产者线程向队列中添加元素时, 如果正有消费者线程等待消费, 则直接将元素传输给生产者线程; 如果没有消费者等待消费, 则将元素存放在队列中, 并一直等到消费者线程消费之后才返回.
    • tryTransfer(): 试着将元素传输给消费者线程, 如果有消费者线程等待接收元素返回true, 否则返回false. 也就是说, tryTransfer()方法无论有没有消费者线程等待接收元素都会立即返回. 如果是tryTransfer(E e, long timeout, TimeUnit unit)方法, 如果没有消费者线程则等待指定时间后再返回.
    public class TestLinkedTransferQueue {
    
        static LinkedTransferQueue<String> queue = new LinkedTransferQueue<String>();
        
        public static void main(String[] args) {
            
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        queue.transfer("hello world");
                        System.out.println(Thread.currentThread().getName() + " end.");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "producer").start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("queue size: " + queue.size());
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        System.out.println(queue.take());
                        System.out.println(Thread.currentThread().getName() + " end.");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "consumer").start();
        }
    }
    

    一般情况下, 使用LinkedTransferQueue, 调用transfer方法传输元素时, 都会有一个消费者等在那儿, 准备接收这个元素.

  • 相关阅读:
    js 左键点击页面时显示“您好”,右键点击时显示“禁止右键”。并在2分钟后自动关闭页面。
    搜藏 SQL
    邮件发送 图片
    超市购物打印小票的简单程序 记录下来
    KFC打印
    printf和scanf对于各种格式说明符
    Unity3d Camera size
    C#笔记
    CSV文件读写注意
    cocos2dx相关网址
  • 原文地址:https://www.cnblogs.com/lzj0616/p/8964474.html
Copyright © 2020-2023  润新知