概述
LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。LinkedBlockingQueue采用可重入锁(ReentrantLock)来保证在并发情况下的线程安全。
构造器
LinkedBlockingQueue一共有三个构造器,分别是无参构造器、可以指定容量的构造器、可以穿入一个容器的构造器。如果在创建实例的时候调用的是无参构造器,LinkedBlockingQueue的默认容量是Integer.MAX_VALUE,这样做很可能会导致队列还没有满,但是内存却已经满了的情况(内存溢出)。
1 public LinkedBlockingQueue(); //设置容量为Integer.MAX 2 3 public LinkedBlockingQueue(int capacity); //设置指定容量 4 5 public LinkedBlockingQueue(Collection<? extends E> c); //穿入一个容器,如果调用该构造器,容量默认也是Integer.MAX_VALUE
LinkedBlockingQueue常用操作
取数据
take():首选。当队列为空时阻塞
poll():弹出队顶元素,队列为空时,返回空
peek():和poll烈性,返回队队顶元素,但顶元素不弹出。队列为空时返回null
remove(Object o):移除某个元素,队列为空时抛出异常。成功移除返回true
添加数据
put():首选。队满是阻塞
offer():队满时返回false
判断队列是否为空
size()方法会遍历整个队列,时间复杂度为O(n),所以最好选用isEmtpy
put元素原理
基本过程:
1.判断元素是否为null,为null抛出异常
2.加锁(可中断锁)
3.判断队列长度是否到达容量,如果到达一直等待
4.如果没有队满,enqueue()在队尾加入元素
5.队列长度加1,此时如果队列还没有满,调用signal唤醒其他堵塞队列
1 if (e == null) throw new NullPointerException(); 2 3 int c = -1; 4 Node<E> node = new Node<E>(e); 5 final ReentrantLock putLock = this.putLock; 6 final AtomicInteger count = this.count; 7 putLock.lockInterruptibly(); 8 try { 9 while (count.get() == capacity) { 10 notFull.await(); 11 } 12 enqueue(node); 13 c = count.getAndIncrement(); 14 if (c + 1 < capacity) 15 notFull.signal(); 16 } finally { 17 putLock.unlock(); 18 }
take元素原理
基本过程:
1.加锁(依旧是ReentrantLock),注意这里的锁和写入是不同的两把锁
2.判断队列是否为空,如果为空就一直等待
3.通过dequeue方法取得数据
3.取走元素后队列是否为空,如果不为空唤醒其他等待中的队列
1 public E take() throws InterruptedException { 2 E x; 3 int c = -1; 4 final AtomicInteger count = this.count; 5 final ReentrantLock takeLock = this.takeLock; 6 takeLock.lockInterruptibly(); 7 try { 8 while (count.get() == 0) { 9 notEmpty.await(); 10 } 11 x = dequeue(); 12 c = count.getAndDecrement(); 13 if (c > 1) 14 notEmpty.signal(); 15 } finally { 16 takeLock.unlock(); 17 } 18 if (c == capacity) 19 signalNotFull(); 20 return x; 21 }
enqueue()和dequeue()方法实现都比较简单,无非就是将元素添加到队尾,从队顶取走元素,感兴趣的朋友可以自己去看一下,这里就不粘贴了。
LinkedBlockingQueue与LinkedBlockingDeque比较
LinkedBlockingDeque和LinkedBlockingQueue的相同点在于:
1. 基于链表
2. 容量可选,不设置的话,就是Int的最大值
和LinkedBlockingQueue的不同点在于:
1. 双端链表和单链表
2. 不存在哨兵节点
3. 一把锁+两个条件
实例:
小记:AtomicInteger的getAndIncrment和getAndDcrement()等方法,这些方法分为两步,get和increment(decrement),在get和increment中间可能有其他线程进入,导致多个线程get到的数值是相同的,也会导致多个线程累加后的值其实累加1.在这种情况下,使用volatile也是没有效果的,因为get之后没有对值进行修改,不能触发volatile的效果。
1 public class ProducerAndConsumer { 2 public static void main(String[] args){ 3 4 try{ 5 BlockingQueue queue = new LinkedBlockingQueue(5); 6 7 ExecutorService executor = Executors.newFixedThreadPool(5); 8 Produer producer = new Produer(queue); 9 for(int i=0;i<3;i++){ 10 executor.execute(producer); 11 } 12 executor.execute(new Consumer(queue)); 13 14 executor.shutdown(); 15 }catch (Exception e){ 16 e.printStackTrace(); 17 } 18 19 } 20 } 21 22 class Produer implements Runnable{ 23 24 private BlockingQueue queue; 25 private int nums = 20; //循环次数 26 27 //标记数据编号 28 private static volatile AtomicInteger count = new AtomicInteger(); 29 private boolean isRunning = true; 30 public Produer(){} 31 32 public Produer(BlockingQueue queue){ 33 this.queue = queue; 34 } 35 36 public void run() { 37 String data = null; 38 try{ 39 System.out.println("开始生产数据"); 40 System.out.println("-----------------------"); 41 42 while(nums>0){ 43 nums--; 44 count.decrementAndGet(); 45 46 Thread.sleep(500); 47 System.out.println(Thread.currentThread().getId()+ " :生产者生产了一个数据"); 48 queue.put(count.getAndIncrement()); 49 } 50 }catch(Exception e){ 51 e.printStackTrace(); 52 Thread.currentThread().interrupt(); 53 }finally{ 54 System.out.println("生产者线程退出!"); 55 } 56 } 57 } 58 59 class Consumer implements Runnable{ 60 61 private BlockingQueue queue; 62 private int nums = 20; 63 private boolean isRunning = true; 64 65 public Consumer(){} 66 67 public Consumer(BlockingQueue queue){ 68 this.queue = queue; 69 } 70 71 public void run() { 72 73 System.out.println("消费者开始消费"); 74 System.out.println("-------------------------"); 75 76 while(nums>0){ 77 nums--; 78 try{ 79 while(isRunning){ 80 int data = (Integer)queue.take(); 81 Thread.sleep(500); 82 System.out.println("消费者消费的数据是" + data); 83 } 84 85 }catch(Exception e){ 86 e.printStackTrace(); 87 Thread.currentThread().interrupt(); 88 }finally { 89 System.out.println("消费者线程退出!"); 90 } 91 92 } 93 } 94 }
效果:
1 12 :生产者生产了一个数据 2 11 :生产者生产了一个数据 3 13 :生产者生产了一个数据 4 12 :生产者生产了一个数据 5 消费者消费的数据是-3 6 11 :生产者生产了一个数据 7 13 :生产者生产了一个数据 8 12 :生产者生产了一个数据 9 消费者消费的数据是-3 10 13 :生产者生产了一个数据 11 11 :生产者生产了一个数据 12 12 :生产者生产了一个数据 13 消费者消费的数据是-3 14 13 :生产者生产了一个数据 15 11 :生产者生产了一个数据 16 消费者消费的数据是-3 17 消费者消费的数据是-3
可以看到,有多个producer在生产数据的时候get到的是相同的值。