用户登录入口,网站从哪些方面做优化,汕头网络推广哪里好,河南建设厅二建公示网站首页内容摘自我的学习网站#xff1a;topjavaer.cn 常见的集合有哪些#xff1f;
Java集合类主要由两个接口Collection和Map派生出来的#xff0c;Collection有三个子接口#xff1a;List、Set、Queue。
Java集合框架图如下#xff1a; List代表了有序可重复集合#xff0c… 内容摘自我的学习网站topjavaer.cn 常见的集合有哪些
Java集合类主要由两个接口Collection和Map派生出来的Collection有三个子接口List、Set、Queue。
Java集合框架图如下 List代表了有序可重复集合可直接根据元素的索引来访问Set代表无序不可重复集合只能根据元素本身来访问Queue是队列集合。Map代表的是存储key-value对的集合可根据元素的key来访问value。
集合体系中常用的实现类有ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等实现类。
List 、Set和Map 的区别
List 以索引来存取元素有序的元素是允许重复的可以插入多个nullSet 不能存放重复元素无序的只允许一个nullMap 保存键值对映射List 底层实现有数组、链表两种方式Set、Map 容器有基于哈希存储和红黑树两种方式实现Set 基于 Map 实现Set 里的元素值就是 Map的键值。
ArrayList 了解吗
ArrayList 的底层是动态数组它的容量能动态增长。在添加大量元素前应用可以使用ensureCapacity操作增加 ArrayList 实例的容量。ArrayList 继承了 AbstractList 并实现了 List 接口。
ArrayList 的扩容机制
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化并将原有数组内容复制到新数组中去。默认情况下新的容量会是原容量的1.5倍。以JDK1.8为例说明:
public boolean add(E e) {//判断是否可以容纳e若能则直接添加在末尾若不能则进行扩容然后再把e添加在末尾ensureCapacityInternal(size 1); // Increments modCount!!//将e添加到数组末尾elementData[size] e;return true;}// 每次在add()一个元素时arraylist都需要对这个list的容量进行一个判断。通过ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力经过处理之后将元素存储在数组elementData的尾部private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {//如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值if (elementData DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;}private void ensureExplicitCapacity(int minCapacity) {modCount;// 若ArrayList已有的存储能力满足最低存储要求则返回add直接添加元素如果最低要求的存储能力ArrayList已有的存储能力这就表示ArrayList的存储能力不足因此需要调用 grow();方法进行扩容if (minCapacity - elementData.length 0)grow(minCapacity);}private void grow(int minCapacity) {// 获取elementData数组的内存空间长度int oldCapacity elementData.length;// 扩容至原来的1.5倍int newCapacity oldCapacity (oldCapacity 1);//校验容量是否够if (newCapacity - minCapacity 0)newCapacity minCapacity;//若预设值大于默认的最大值检查是否溢出if (newCapacity - MAX_ARRAY_SIZE 0)newCapacity hugeCapacity(minCapacity);// 调用Arrays.copyOf方法将elementData数组指向新的内存空间//并将elementData的数据复制到新的内存空间elementData Arrays.copyOf(elementData, newCapacity);}怎么在遍历 ArrayList 时移除一个元素
foreach删除会导致快速失败问题可以使用迭代器的 remove() 方法。
Iterator itr list.iterator();
while(itr.hasNext()) {if(itr.next().equals(jay) {itr.remove();}
}Arraylist 和 Vector 的区别
ArrayList在内存不够时扩容为原来的1.5倍Vector是扩容为原来的2倍。Vector属于线程安全级别的但是大多数情况下不使用Vector因为操作Vector效率比较低。 本文内容已经整理到大厂面试手册了手册内容包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题非常实用有小伙伴靠着这份er~ 需要的小伙伴可以自行下载 http://mp.weixin.qq.com/s?__bizMzg2OTY1NzY0MQmid2247485445idx1sn1c6e224b9bb3da457f5ee03894493dbcchksmce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd Arraylist 与 LinkedList的区别
ArrayList基于动态数组实现LinkedList基于链表实现。对于随机index访问的get和set方法ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素LinkedList要移动指针遍历每个元素直到找到为止。新增和删除元素LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时可能扩容和复制数组LinkedList实例化对象需要时间外只需要修改指针即可。
HashMap
HashMap 使用数组链表红黑树JDK1.8增加了红黑树部分实现的 链表长度大于8TREEIFY_THRESHOLD时会把链表转换为红黑树红黑树节点个数小于6UNTREEIFY_THRESHOLD时才转化为链表防止频繁的转化。
解决hash冲突的办法有哪些HashMap用的哪种
解决Hash冲突方法有开放定址法、再哈希法、链地址法。HashMap中采用的是 链地址法 。
开放定址法基本思想就是如果pH(key)出现冲突时则以p为基础再次hashp1H(p),如果p1再次出现冲突则以p1为基础以此类推直到找到一个不冲突的哈希地址pi。 因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素而且因为存在再次hash所以只能在删除的节点上做标记而不能真正删除节点。再哈希法提供多个不同的hash函数当R1H1(key1)发生冲突时再计算R2H2(key1)直到没有冲突为止。 这样做虽然不易产生堆集但增加了计算的时间。链地址法将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。
使用的hash算法
Hash算法取key的hashCode值、高位运算、取模运算。
hkey.hashCode() //第一步 取hashCode值
h^(h16) //第二步 高位参与运算减少冲突
return h(length-1); //第三步 取模运算在JDK1.8的实现中优化了高位运算的算法通过hashCode()的高16位异或低16位实现的这么做可以在数组比较小的时候也能保证考虑到高低位都参与到Hash的计算中可以减少冲突同时不会有太大的开销。
为什么建议设置HashMap的容量
HashMap有扩容机制就是当达到扩容条件时会进行扩容。扩容条件就是当HashMap中的元素个数超过临界值时就会自动扩容threshold loadFactor * capacity。
如果我们没有设置初始容量大小随着元素的不断增加HashMap会发生多次扩容。而HashMap每次扩容都需要重建hash表非常影响性能。所以建议开发者在创建HashMap的时候指定初始化容量。
扩容过程
1.8扩容机制当元素个数大于threshold时会进行扩容使用2倍容量的数组代替原有数组。采用尾插入的方式将原数组元素拷贝到新数组。1.8扩容之后链表元素相对位置没有变化而1.7扩容之后链表元素会倒置。
1.7链表新节点采用的是头插法这样在线程一扩容迁移元素时会将元素顺序改变导致两个线程中出现元素的相互指向而形成循环链表1.8采用了尾插法避免了这种情况的发生。
原数组的元素在重新计算hash之后因为数组容量n变为2倍那么n-1的mask范围在高位多1bit。在元素拷贝过程不需要重新计算元素在数组中的位置只需要看看原来的hash值新增的那个bit是1还是0是0的话索引没变是1的话索引变成“原索引oldCap”根据e.hash oldCap 0判断 。这样可以省去重新计算hash值的时间而且由于新增的1bit是0还是1可以认为是随机的因此resize的过程会均匀的把之前的冲突的节点分散到新的bucket。 如果文章对你有用可以点赞支持一下我 put方法流程
如果table没有初始化就先进行初始化过程使用hash算法计算key的索引判断索引处有没有存在元素没有就直接插入如果索引处存在元素则遍历插入有两种情况一种是链表形式就直接遍历到尾端插入一种是红黑树就按照红黑树结构插入链表的数量大于阈值8就要转换成红黑树的结构添加成功后会检查是否需要扩容 红黑树的特点
每个节点或者是黑色或者是红色。根节点和叶子节点NIL是黑色的。如果一个节点是红色的则它的子节点必须是黑色的。从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
在解决 hash 冲突的时候为什么选择先用链表再转红黑树?
因为红黑树需要进行左旋右旋变色这些操作来保持平衡而单链表不需要。所以当元素个数小于8个的时候采用链表结构可以保证查询性能。而当元素个数大于8个的时候并且数组容量大于等于64会采用红黑树结构。因为红黑树搜索时间复杂度是 O(logn)而链表是 O(n)在n比较大的时候使用红黑树可以加快查询速度。
HashMap 的长度为什么是 2 的幂次方
Hash 值的范围值比较大使用之前需要先对数组的长度取模运算得到的余数才是元素存放的位置也就是对应的数组下标。这个数组下标的计算方法是(n - 1) hash。将HashMap的长度定为2 的幂次方这样就可以使用(n - 1)hash位运算代替%取余的操作提高性能。
HashMap默认加载因子是多少为什么是 0.75
先看下HashMap的默认构造函数
int threshold; // 容纳键值对的最大值
final float loadFactor; // 负载因子
int modCount;
int size; Node[] table的初始化长度length为16默认的loadFactor是0.750.75是对空间和时间效率的一个平衡选择根据泊松分布loadFactor 取0.75碰撞最小。一般不会修改除非在时间和空间比较特殊的情况下 如果内存空间很多而又对时间效率要求很高可以降低负载因子Load factor的值 。 如果内存空间紧张而对时间效率要求不高可以增加负载因子loadFactor的值这个值可以大于1。
一般用什么作为HashMap的key?
一般用Integer、String这种不可变类当 HashMap 当 key。String类比较常用。
因为 String 是不可变的所以在它创建的时候hashcode就被缓存了不需要重新计算。这就是 HashMap 中的key经常使用字符串的原因。获取对象的时候要用到 equals() 和 hashCode() 方法而Integer、String这些类都已经重写了 hashCode() 以及 equals() 方法不需要自己去重写这两个方法。
HashMap为什么线程不安全
多线程下扩容死循环。JDK1.7中的 HashMap 使用头插法插入元素在多线程的环境下扩容的时候有可能导致环形链表的出现形成死循环。在JDK1.8中在多线程环境下会发生数据覆盖的情况。
HashMap和HashTable的区别
HashMap和Hashtable都实现了Map接口。
HashMap可以接受为null的key和valuekey为null的键值对放在下标为0的头结点的链表中而Hashtable则不行。HashMap是非线程安全的HashTable是线程安全的。Jdk1.5提供了ConcurrentHashMap它是HashTable的替代。Hashtable很多方法是同步方法在单线程环境下它比HashMap要慢。哈希值的使用不同HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
LinkedHashMap底层原理
HashMap是无序的迭代HashMap所得到元素的顺序并不是它们最初放到HashMap的顺序即不能保持它们的插入顺序。
LinkedHashMap继承于HashMap是HashMap和LinkedList的融合体具备两者的特性。每次put操作都会将entry插入到双向链表的尾部。
讲一下TreeMap
TreeMap是一个能比较元素大小的Map集合会对传入的key进行了大小排序。可以使用元素的自然顺序也可以使用集合中自定义的比较器来进行排序。
public class TreeMapK,Vextends AbstractMapK,Vimplements NavigableMapK,V, Cloneable, java.io.Serializable {
}TreeMap 的继承结构 TreeMap的特点
TreeMap是有序的key-value集合通过红黑树实现。根据键的自然顺序进行排序或根据提供的Comparator进行排序。TreeMap继承了AbstractMap实现了NavigableMap接口支持一系列的导航方法给定具体搜索目标可以返回最接近的匹配项。如floorEntry()、ceilingEntry()分别返回小于等于、大于等于给定键关联的Map.Entry()对象不存在则返回null。lowerKey()、floorKey、ceilingKey、higherKey()只返回关联的key。
HashSet底层原理
HashSet 基于 HashMap 实现。放入HashSet中的元素实际上由HashMap的key来保存而HashMap的value则存储了一个静态的Object对象。
public class HashSetEextends AbstractSetEimplements SetE, Cloneable, java.io.Serializable {static final long serialVersionUID -5024744406713321676L;private transient HashMapE,Object map; //基于HashMap实现//...
}HashSet、LinkedHashSet 和 TreeSet 的区别
HashSet 是 Set 接口的主要实现类 HashSet 的底层是 HashMap线程不安全的可以存储 null 值
LinkedHashSet 是 HashSet 的子类能够按照添加的顺序遍历
TreeSet 底层使用红黑树能够按照添加元素的顺序进行遍历排序的方式可以自定义。
什么是fail fast
fast-fail是Java集合的一种错误机制。当多个线程对同一个集合进行操作时就有可能会产生fast-fail事件。例如当线程a正通过iterator遍历集合时另一个线程b修改了集合的内容此时modCount记录集合操作过程的修改次数会加1不等于expectedModCount那么线程a访问集合的时候就会抛出ConcurrentModificationException产生fast-fail事件。边遍历边修改集合也会产生fast-fail事件。
解决方法
使用Colletions.synchronizedList方法或在修改集合内容的地方加上synchronized。这样的话增删集合内容的同步锁会阻塞遍历操作影响性能。使用CopyOnWriteArrayList来替换ArrayList。在对CopyOnWriteArrayList进行修改操作的时候会拷贝一个新的数组对新的数组进行操作操作完成后再把引用移到新的数组。
什么是fail safe
采用安全失败机制的集合容器在遍历时不是直接在集合内容上访问的而是先复制原有集合内容在拷贝的集合上进行遍历。java.util.concurrent包下的容器都是安全失败可以在多线程下并发使用并发修改。
原理由于迭代时是对原集合的拷贝进行遍历所以在遍历过程中对原集合所作的修改并不能被迭代器检测到所以不会触发Concurrent Modification Exception。
缺点基于拷贝内容的优点是避免了Concurrent Modification Exception但同样地迭代器并不能访问到修改后的内容即迭代器遍历的是开始遍历那一刻拿到的集合拷贝在遍历期间原集合发生的修改迭代器是不知道的。
讲一下ArrayDeque
ArrayDeque实现了双端队列内部使用循环数组实现默认大小为16。它的特点有 在两端添加、删除元素的效率较高 根据元素内容查找和删除的效率比较低。 没有索引位置的概念不能根据索引位置进行操作。
ArrayDeque和LinkedList都实现了Deque接口如果只需要从两端进行操作ArrayDeque效率更高一些。如果同时需要根据索引位置进行操作或者经常需要在中间进行插入和删除LinkedList有相应的 api如add(int index, E e)则应该选LinkedList。
ArrayDeque和LinkedList都是线程不安全的可以使用Collections工具类中synchronizedXxx()转换成线程同步。
哪些集合类是线程安全的哪些不安全
线性安全的集合类
Vector比ArrayList多了同步机制。Hashtable。ConcurrentHashMap是一种高效并且线程安全的集合。Stack栈也是线程安全的继承于Vector。
线性不安全的集合类
HashmapArraylistLinkedListHashSetTreeSetTreeMap
迭代器 Iterator 是什么
Iterator模式用同一种逻辑来遍历集合。它可以把访问逻辑从不同类型的集合类中抽象出来不需要了解集合内部实现便可以遍历集合元素统一使用 Iterator 提供的接口去遍历。它的特点是更加安全因为它可以保证在当前遍历的集合元素被更改的时候就会抛出 ConcurrentModificationException 异常。
public interface CollectionE extends IterableE {IteratorE iterator();
}主要有三个方法hasNext()、next()和remove()。
Iterator 和 ListIterator 有什么区别
ListIterator 是 Iterator的增强版。
ListIterator遍历可以是逆向的因为有previous()和hasPrevious()方法而Iterator不可以。ListIterator有add()方法可以向List添加对象而Iterator却不能。ListIterator可以定位当前的索引位置因为有nextIndex()和previousIndex()方法而Iterator不可以。ListIterator可以实现对象的修改set()方法可以实现。Iierator仅能遍历不能修改。ListIterator只能用于遍历List及其子类Iterator可用来遍历所有集合。
如何让一个集合不能被修改
可以采用Collections包下的unmodifiableMap/unmodifiableList/unmodifiableSet方法通过这个方法返回的集合是不可以修改的。如果修改的话会抛出 java.lang.UnsupportedOperationException异常。
ListString list new ArrayList();
list.add(x);
CollectionString clist Collections.unmodifiableCollection(list);
clist.add(y); // 运行时此行报错
System.out.println(list. size());对于List/Set/Map集合Collections包都有相应的支持。
那使用final关键字进行修饰可以实现吗
答案是不可以。
final关键字修饰的成员变量如果是是引用类型的话则表示这个引用的地址值是不能改变的但是这个引用所指向的对象里面的内容还是可以改变的。
而集合类都是引用类型用final修饰的话集合里面的内容还是可以修改的。
并发容器
JDK 提供的这些容器大部分在 java.util.concurrent 包中。
ConcurrentHashMap: 线程安全的 HashMapCopyOnWriteArrayList: 线程安全的 List在读多写少的场合性能非常好远远好于 Vector.ConcurrentLinkedQueue: 高效的并发队列使用链表实现。可以看做一个线程安全的 LinkedList这是一个非阻塞队列。BlockingQueue: 阻塞队列接口JDK 内部通过链表、数组等方式实现了这个接口。非常适合用于作为数据共享的通道。ConcurrentSkipListMap: 跳表的实现。使用跳表的数据结构进行快速查找。
ConcurrentHashMap
多线程环境下使用Hashmap进行put操作会引起死循环应该使用支持多线程的 ConcurrentHashMap。
JDK1.8 ConcurrentHashMap取消了segment分段锁而采用CAS和synchronized来保证并发安全。数据结构采用数组链表/红黑二叉树。synchronized只锁定当前链表或红黑二叉树的首节点相比1.7锁定HashEntry数组锁粒度更小支持更高的并发量。当链表长度过长时Node会转换成TreeNode提高查找速度。
put执行流程
在put的时候需要锁住Segment保证并发安全。调用get的时候不加锁因为node数组成员val和指针next是用volatile修饰的更改后的值会立刻刷新到主存中保证了可见性node数组table也用volatile修饰保证在运行过程对其他线程具有可见性。
transient volatile NodeK,V[] table;static class NodeK,V implements Map.EntryK,V {volatile V val;volatile NodeK,V next;
}put 操作流程
如果table没有初始化就先进行初始化过程使用hash算法计算key的位置如果这个位置为空则直接CAS插入如果不为空的话则取出这个节点来如果取出来的节点的hash值是MOVED(-1)的话则表示当前正在对这个数组进行扩容复制到新的数组则当前线程也去帮助复制如果这个节点不为空也不在扩容则通过synchronized来加锁进行添加操作这里有两种情况一种是链表形式就直接遍历到尾端插入或者覆盖掉相同的key一种是红黑树就按照红黑树结构插入链表的数量大于阈值8就会转换成红黑树的结构或者进行扩容table长度小于64添加成功后会检查是否需要扩容
怎么扩容
数组扩容transfer方法中会设置一个步长表示一个线程处理的数组长度最小值是16。在一个步长范围内只有一个线程会对其进行复制移动操作。
ConcurrentHashMap 和 Hashtable 的区别
Hashtable通过使用synchronized修饰方法的方式来实现多线程同步因此Hashtable的同步会锁住整个数组。在高并发的情况下性能会非常差。ConcurrentHashMap采用了更细粒度的锁来提高在并发情况下的效率。注synchronized容器同步容器也是通过synchronized关键字来实现线程安全在使用的时候会对所有的数据加锁。Hashtable默认的大小为11当达到阈值后每次按照下面的公式对容量进行扩充newCapacity oldCapacity * 2 1。ConcurrentHashMap默认大小是16扩容时容量扩大为原来的2倍。
CopyOnWrite
Copy-On-Write写时复制。当我们往容器添加元素时不直接往容器添加而是先将当前容器进行复制复制出一个新的容器然后往新的容器添加元素添加完元素之后再将原容器的引用指向新容器。这样做的好处就是可以对CopyOnWrite容器进行并发的读而不需要加锁因为当前容器不会被修改。 public boolean add(E e) {final ReentrantLock lock this.lock;lock.lock(); //add方法需要加锁try {Object[] elements getArray();int len elements.length;Object[] newElements Arrays.copyOf(elements, len 1); //复制新数组newElements[len] e;setArray(newElements); //原容器的引用指向新容器return true;} finally {lock.unlock();}}从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器它们是CopyOnWriteArrayList和CopyOnWriteArraySet。
缺点
内存占用问题。由于CopyOnWrite的写时复制机制在进行写操作的时候内存里会同时驻扎两个对象的内存。CopyOnWrite容器不能保证数据的实时一致性可能读取到旧数据。
CopyOnWriteArrayList
CopyOnWriteArrayList是Java并发包中提供的一个并发容器。CopyOnWriteArrayList相当于线程安全的ArrayListCopyOnWriteArrayList使用了一种叫写时复制的方法当有新元素add到CopyOnWriteArrayList时先从原有的数组中拷贝一份出来然后在新的数组做写操作写完之后再将原来的数组引用指向到新数组。
CopyOnWriteArrayList中add方法添加的时候是需要加锁的保证同步避免了多线程写的时候复制出多个副本。读的时候不需要加锁如果读的时候有其他线程正在向CopyOnWriteArrayList添加数据还是可以读到旧的数据。
CopyOnWrite并发容器用于读多写少的并发场景。
优点
读操作性能很高因为无需任何同步措施比较适用于读多写少的并发场景。Java的list在遍历时若中途有别的线程对list容器进行修改则会抛出ConcurrentModificationException异常。而CopyOnWriteArrayList由于其读写分离的思想遍历和修改操作分别作用在不同的list容器所以在使用迭代器进行遍历时候也就不会抛出ConcurrentModificationException异常了。
缺点
一是内存占用问题毕竟每次执行写操作都要将原容器拷贝一份数据量大时对内存压力较大可能会引起频繁GC
二是无法保证实时性Vector对于读写操作均加锁同步可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因写和读分别作用在新老不同容器上在写操作执行过程中读不会阻塞但读取到的却是老容器的数据。
ConcurrentLinkedQueue
非阻塞队列。高效的并发队列使用链表实现。可以看做一个线程安全的 LinkedList通过 CAS 操作实现。
如果对队列加锁的成本较高则适合使用无锁的 ConcurrentLinkedQueue 来替代。适合在对性能要求相对较高同时有多个线程对队列进行读写的场景。
非阻塞队列中的几种主要方法 add(E e): 将元素e插入到队列末尾如果插入成功则返回true如果插入失败即队列已满则会抛出异常 remove()移除队首元素若移除成功则返回true如果移除失败队列为空则会抛出异常 offer(E e)将元素e插入到队列末尾如果插入成功则返回true如果插入失败即队列已满则返回false poll()移除并获取队首元素若成功则返回队首元素否则返回null peek()获取队首元素若成功则返回队首元素否则返回null
对于非阻塞队列一般情况下建议使用offer、poll和peek三个方法不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否而使用add和remove方法却不能达到这样的效果。
阻塞队列
阻塞队列是java.util.concurrent包下重要的数据结构BlockingQueue提供了线程安全的队列访问方式当阻塞队列进行插入数据时如果队列已满线程将会阻塞等待直到队列非满从阻塞队列取数据时如果队列已空线程将会阻塞等待直到队列非空。并发包下很多高级同步类的实现都是基于BlockingQueue实现的。BlockingQueue 适合用于作为数据共享的通道。
使用阻塞算法的队列可以用一个锁入队和出队用同一把锁或两个锁入队和出队用不同的锁等方式来实现。
阻塞队列和一般的队列的区别就在于
多线程支持多个线程可以安全的访问队列阻塞操作当队列为空的时候消费线程会阻塞等待队列不为空当队列满了的时候生产线程就会阻塞直到队列不满
方法
方法\处理方式抛出异常返回特殊值一直阻塞超时退出插入方法add(e)offer(e)put(e)offer(e,time,unit)移除方法remove()poll()take()poll(time,unit)检查方法element()peek()不可用不可用
JDK提供的阻塞队列
JDK 7 提供了7个阻塞队列如下
1、ArrayBlockingQueue
有界阻塞队列底层采用数组实现。ArrayBlockingQueue 一旦创建容量不能改变。其并发控制采用可重入锁来控制不管是插入操作还是读取操作都需要获取到锁才能进行操作。此队列按照先进先出FIFO的原则对元素进行排序。默认情况下不能保证线程访问队列的公平性参数fair可用于设置线程是否公平访问队列。为了保证公平性通常会降低吞吐量。
private static ArrayBlockingQueueInteger blockingQueue new ArrayBlockingQueueInteger(10,true);//fair2、LinkedBlockingQueue
LinkedBlockingQueue是一个用单向链表实现的有界阻塞队列可以当做无界队列也可以当做有界队列来使用。通常在创建 LinkedBlockingQueue 对象时会指定队列最大的容量。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。与 ArrayBlockingQueue 相比起来具有更高的吞吐量。
3、PriorityBlockingQueue
支持优先级的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则或者初始化PriorityBlockingQueue时指定构造参数Comparator来进行排序。
PriorityBlockingQueue 只能指定初始的队列大小后面插入元素的时候如果空间不够的话会自动扩容。
PriorityQueue 的线程安全版本。不可以插入 null 值同时插入队列的对象必须是可比较大小的comparable否则报 ClassCastException 异常。它的插入操作 put 方法不会 block因为它是无界队列take 方法在队列为空的时候会阻塞。
4、DelayQueue
支持延时获取元素的无界阻塞队列。队列使用PriorityBlockingQueue来实现。队列中的元素必须实现Delayed接口在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
5、SynchronousQueue
不存储元素的阻塞队列每一个put必须等待一个take操作否则不能继续添加元素。支持公平访问队列。
SynchronousQueue可以看成是一个传球手负责把生产者线程处理的数据直接传递给消费者线程。队列本身不存储任何元素非常适合传递性场景。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。
6、LinkedTransferQueue
由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列多了tryTransfer和transfer方法。
transfer方法如果当前有消费者正在等待接收元素take或者待时间限制的poll方法transfer可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素则将元素放在队列的tail节点并等到该元素被消费者消费了才返回。
tryTransfer方法用来试探生产者传入的元素能否直接传给消费者。如果没有消费者在等待则返回false。和上述方法的区别是该方法无论消费者是否接收方法立即返回。而transfer方法是必须等到消费者消费了才返回。
原理
JDK使用通知模式实现阻塞队列。所谓通知模式就是当生产者往满的队列里添加元素时会阻塞生产者当消费者消费了一个队列中的元素后会通知生产者当前队列可用。
ArrayBlockingQueue使用Condition来实现
private final Condition notEmpty;
private final Condition notFull;public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity 0)throw new IllegalArgumentException();this.items new Object[capacity];lock new ReentrantLock(fair);notEmpty lock.newCondition();notFull lock.newCondition();
}public E take() throws InterruptedException {final ReentrantLock lock this.lock;lock.lockInterruptibly();try {while (count 0) // 队列为空时阻塞当前消费者notEmpty.await();return dequeue();} finally {lock.unlock();}
}public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock this.lock;lock.lockInterruptibly();try {while (count items.length)notFull.await();enqueue(e);} finally {lock.unlock();}
}private void enqueue(E x) {final Object[] items this.items;items[putIndex] x;if (putIndex items.length)putIndex 0;count;notEmpty.signal(); // 队列不为空时通知消费者获取元素
}最后给大家分享一个Github仓库上面有大彬整理的300多本经典的计算机书籍PDF包括C语言、C、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等可以star一下下次找书直接在上面搜索仓库持续更新中~ Github地址
如果访问不了Github可以访问码云地址。
码云地址