Java并发队列-Queue

目录

ConcurrentLinkedQueue

BlockingQueue接口


在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪一种实现都继承自Queue。

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一种适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。通常,ConcurrentLinkedQueue性能好于BlockingQueue,它是一个基于链接节点的无界线程安全队列。该队列遵循先进先出的原则,但不允许添加null的元素。

ConcurrentLinkedQueue重要方法:

  • add()和offer()都是加入元素的方法,在ConcurrentLinkedQueue中,这两个方法没有任何区别。
  • poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会。

实例代码如下:

public class UseQueue {

    public static void main(String[] args) throws Exception {

        //高性能无阻塞无界队列:ConcurrentLinkedQueue
        ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<String>();
        q.offer("a");
        q.offer("b");
        q.offer("c");
        q.offer("d");
        q.add("e");

        System.out.println(q.poll());    //a 从头部取出元素,并从队列里删除
        System.out.println(q.size());    //4
        System.out.println(q.peek());    //b
        System.out.println(q.size());    //4

    }
}

输出结果如下:

a
4
b
4

 

BlockingQueue接口

阻塞队列是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。支持阻塞的插入方法意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满;支持阻塞的移除方法意思是在队列为空时,获取元素的线程会等待队列变为非空。

阻塞队列常常用于生产者和消费者的场景,生产者向队列里添加元素的线程,消费者从队列里获取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。在阻塞队列不可用时,这两个附加操作提供了4种处理方式。如下表所示:

  • 抛出异常:当队列满时,如果再往队列里面插入元素,会抛出IllegalStateException异常。当队列为空时,从队列里获取元素会抛出NoSuchElementException异常。
  • 返回特殊值:当往队列插入元素时,会返回元素是否插入成功,成功返回true。是移除方法,则是从队列里取出一个元素,如果没有则返回null。
  • 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。
  • 超时退出:当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,如果超过了指定的时间,生产者线程就会退出。

如果使用无界阻塞队列,队列不可能会出现满的情况,所以使用put或offer方法永远不会被阻塞,而且使用offer方法时,该方法永远返回true。

 

BlockingQueue主要有以下的几种实现:

  • ArrayBlockingQueue:基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长的数组,以便缓存队列中的数据对象,其内部没有实现读写分离,也就意味着生产和消费不能完全并行。长度需要在构造时指定,可以指定是先进先出还是先进后出,也叫有界队列,在很多场合非常适合使用。
  • LinkedBlockingQueue:基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作的完全并行运行。它是一个无界队列
  • SynchronousQueue:一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并消费。因此生产者线程对其的插入操作put,必须等待消费者的移除操作take,反过来也一样。数据并不会缓存到SynchronousQueue。
  • PriorityBlockingQueue:基于优先级的阻塞队列,优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口。这里需要注意的是,PriorityBlockingQueue并不会在元素插入的时候,对元素进行优先级排序,而是每次执行移出操作的时候,将下次要被移出的元素做优先级排序,放到队列的头部。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,它也是一个无界队列。
  • DelayQueue:带有延迟时间的Queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。

BlockingQueue的核心方法如下:
放入数据:

  • offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
  • offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
  • put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.

获取数据:

  • poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
  • poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
  • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入; 
  • drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

ArrayBlockingQueue的实例代码如下:

public class UseQueue {

    public static void main(String[] args) throws Exception {

        ArrayBlockingQueue<String> array = new ArrayBlockingQueue<String>(5);
        array.put("a");
        array.put("b");
        array.add("c");
        array.add("d");
        array.add("e");
        System.out.println(array.offer("a", 3, TimeUnit.SECONDS));
        array.add("f");

    }
}

输出结果为:

false
Exception in thread "main" java.lang.IllegalStateException: Queue full
	at java.util.AbstractQueue.add(AbstractQueue.java:98)
	at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
	at com.bjsxt.base.coll013.UseQueue.main(UseQueue.java:23)

从上面的代码中,ArrayBlockingQueue中有put、add和offer三个方法都是往队列中添加元素。当队列元素达到5个元素的时候,使用array.offer("a", 3, TimeUnit.SECONDS)继续添加元素,当3秒过后仍然没有将元素添加进去,就会报出false,而使用add添加元素的时候会报出异常:Queue full。

LinkedBlockingQueue的实例代码如下:

public class UseQueue {

    public static void main(String[] args) throws Exception {


        //阻塞队列
        LinkedBlockingQueue<String> q = new LinkedBlockingQueue<String>();
        q.offer("a");
        q.offer("b");
        q.offer("c");
        q.offer("d");
        q.offer("e");
        q.add("f");
        System.out.println("LinkedBlockingQueue元素的个数为:" + q.size());
        System.out.print("LinkedBlockingQueue中的元素为:");
        for (Iterator iterator = q.iterator(); iterator.hasNext(); ) {
            String string = (String) iterator.next();
            System.out.print(string + " ");
        }
        System.out.println();
        List<String> list = new ArrayList<String>();
        System.out.println("从阻塞队列一次性获取元素的个数:" + q.drainTo(list, 3));
        System.out.println("获取的元素的大小为:" + list.size());
        System.out.print("获取到的元素为:");
        for (String string : list) {
            System.out.print(string + " ");
        }
        System.out.println();

        System.out.println("LinkedBlockingQueue元素为:" + q);

    }
}

输出结果如下:

LinkedBlockingQueue元素的个数为:6
LinkedBlockingQueue中的元素为:a b c d e f 
从阻塞队列一次性获取元素的个数:3
获取的元素的大小为:3
获取到的元素为:a b c 
LinkedBlockingQueue元素为:[d, e, f]

SynchronousQueue的实例代码如下:

public class UseQueue {

    public static void main(String[] args) throws Exception {

        //没有容量
        final SynchronousQueue<String> q = new SynchronousQueue<String>();
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    q.put("Hello World");
                    q.put("Hello World");
                    System.out.println(q.size());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(q.take());
                    System.out.println(q.take());

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
    }
}

输出的结果为:

Hello World
0
Hello World

PriorityBlockingQueue的实现实例代码:

我们先定义一个比较对象Task

public class Task implements Comparable<Task>{
	
	private int id ;
	private String name;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public int compareTo(Task task) {
		return this.id > task.id ? 1 : (this.id < task.id ? -1 : 0);  
	}
	
	public String toString(){
		return this.id + "," + this.name;
	}
	
}

定义优先级队列的实现类:

public class UsePriorityBlockingQueue {

    public static void main(String[] args) throws Exception {
        PriorityBlockingQueue<Task> q = new PriorityBlockingQueue<Task>();

        Task t1 = new Task();
        t1.setId(1);
        t1.setName("id为1");
        Task t2 = new Task();
        t2.setId(2);
        t2.setName("id为2");
        Task t3 = new Task();
        t3.setId(3);
        t3.setName("id为3");

        q.add(t3);
        q.add(t2);
        q.add(t1);

        // 1 3 4
        System.out.println("容器:" + q);
        System.out.println(q.take().getId());
        System.out.println("容器:" + q);
    }
}

输出的结果为:

容器:[1,id为1, 3,id为3, 2,id为2]
1
容器:[2,id为2, 3,id为3]

DelayQueue的实例代码如下:

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class Wangmin implements Delayed {  
    
    private String name;  
    //身份证  
    private String id;  
    //截止时间  
    private long endTime;  
    //定义时间工具类
    private TimeUnit timeUnit = TimeUnit.SECONDS;
      
    public Wangmin(String name,String id,long endTime){  
        this.name=name;  
        this.id=id;  
        this.endTime = endTime;  
    }  
      
    public String getName(){  
        return this.name;  
    }  
      
    public String getId(){  
        return this.id;  
    }  
      
    /** 
     * 用来判断是否到了截止时间 
     */  
    @Override  
    public long getDelay(TimeUnit unit) { 
    	return endTime - System.currentTimeMillis();
    }  
  
    /** 
     * 相互批较排序用 
     */  
    @Override  
    public int compareTo(Delayed delayed) {  
    	Wangmin w = (Wangmin)delayed;  
        return this.getDelay(this.timeUnit) - w.getDelay(this.timeUnit) > 0 ? 1:0;  
    }
}  
import java.util.concurrent.DelayQueue;

public class WangBa implements Runnable {

    private DelayQueue<Wangmin> queue = new DelayQueue<Wangmin>();
    public boolean yinye = true;

    public void shangji(String name, String id, int money) {
        Wangmin man = new Wangmin(name, id, 1000 * money + System.currentTimeMillis());
        System.out.println("网名" + man.getName() + " 身份证" + man.getId() + "交钱" + money + "块,开始上机...");
        this.queue.add(man);
    }

    public void xiaji(Wangmin man) {
        System.out.println("网名" + man.getName() + " 身份证" + man.getId() + "时间到下机...");
    }

    @Override
    public void run() {
        while (yinye) {
            try {
                Wangmin man = queue.take();
                xiaji(man);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String args[]) {
        try {
            System.out.println("网吧开始营业");
            WangBa siyu = new WangBa();
            Thread shangwang = new Thread(siyu);
            shangwang.start();

            siyu.shangji("路人甲", "123", 1);
            siyu.shangji("路人乙", "234", 10);
            siyu.shangji("路人丙", "345", 5);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}  

运行的结果如下:

网吧开始营业
网名路人甲 身份证123交钱1块,开始上机...
网名路人乙 身份证234交钱10块,开始上机...
网名路人丙 身份证345交钱5块,开始上机...
网名路人甲 身份证123时间到下机...
网名路人丙 身份证345时间到下机...
网名路人乙 身份证234时间到下机...

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值