java的容器

https://docs.oracle.com/javase/tutorial/collections/interfaces/index.html

使用容器类时定义初始大小是个好习惯

说明:对于以上的框架图有如下几点说明

1、所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。

2、集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。

3、抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。

4、实现类:8个实现类(实线表示),对接口的具体实现。

5、Collection 接口是一组允许重复的对象。

6、Set 接口继承 Collection,集合元素不重复。

7、List 接口继承 Collection,允许重复,维护元素插入顺序。

8、Map接口是键-值对象,与Collection接口没有什么关系。

9、Set、List和Map可以看做集合的三大类:

  • List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。

  • Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。

  • Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。

Collection 接口的直接子接口

1 Set 接口

     1.1EnumSet(抽象类),实现类JumboEnumSet(大于64个)、RegularEnumSet(小于等于64个).所有元素必须来源于同一枚举

     1.2SortedSet(接口) 实现类TreeSet(基于红黑树),线程不安全。不能放入null元素。

     1.3HashSet(实现类) 线程不安全

          1.3.1LinkedHashSet(实现类)线程不安全。使用双向链表维持元素的插入顺序

     1.4ConcurrentSkipListSet

     1.5CopyOnWriteArraySet 读操作不加锁,写操作时会复制一个分新的数组

CopyOnWriteArraySet is a Set implementation backed up by a copy-on-write array. All mutative operations, such as add, set, and remove, are implemented by making a new copy of the array; no locking is ever required. Even iteration may safely proceed concurrently with element insertion and deletion. Unlike most Set implementations, the add, remove, and contains methods require time proportional to the size of the set. This implementation is only appropriate for sets that are rarely modified but frequently iterated. It is well suited to maintaining event-handler lists that must prevent duplicates.

2 Queue接口   干货 | 45张图庖丁解牛18种Queue,你知道几种?

     2.1Deque接口

               2.1.1ArrayDeque(实现类)

               2.2.2LinkedList(实现类),线程不安全 也继承了ArrayList实现类

               2.2.3CocurrentLinkedDeque  无锁的、线程安全的、性能高效的、基于链表结构实现的双向队列(在JDK1.7版本中被引入)。

     2.2PriorityQueue(实现类)不能放入空元素

     2.3BlockingQueue接口

          2.3.1 BlockingDeque接口

               2.3.1.1LinkedBlockingDeque

          2.3.2 ArrayBlockingQueue类

          2.3.3DelayBlockingQueue

          2.3.4PriorityBlockingQueque

          2.3.5SynchronousQueue 

     2.4ConcurrentLinkedQueue  非阻塞算法实现线程安全的队列。无锁的、线程安全的、性能高效的、基于链表结构实现的FIFO单向队列(在JDK1.5版本中被引入)

3 List接口

     3.1ArrayList类 线程不安全

     3.2LinkedList类,也继承了Deque接口  双向链表

     3.3 Vector 不推荐使用,线程安全。

          3.3.1Stack类,线程安全

     3.4 CopyOnWriteArrayList 读操作不加锁,写操作时会复制一个分新的数组。允许多线程遍历,但是其iterator不支持对数据进行修改

核心思想是利用读写分离,因为高并发往往是读多写少。进行读操作的时候,不加锁以保证性能;对写操作则要加ReentrantLock锁,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性。

既然CopyOnWrite在进行写操作的时候要进行数组的复制,性能和内存开销比较大,因此它更适用于读多写少的操作,例如缓存

CopyOnWrite分析_登天蚂蚁的博客-CSDN博客_copyonwrite

不能保证实时数据一致性,只能保证最终一致性。

CopyOnWrite容器,简称COW,该容器的基本实现思路是在程序运行的初期,所有的线程都共享一个数据集合的引用。所有线程对该容器的读取操作将不会对数据集合产生加锁的动作,从而使得高并发高吞吐量的读取操作变得高效,但是当有线程对该容器中的数据集合进行删除或增加等写操作时才会对整个数据集合进行加锁操作,然后将容器中的数据集合复制一份,并且基于最新的复制进行删除或增加等写操作,当写操作执行结束以后,将最新复制的数据集合引用指向原有的数据集合,进而达到读写分离最终一致性的目的。

我们在前面也提到过这样做的好处是多线程对CopyOnWrite容器进行并发的读是不需要加锁的,因为当前容器中的数据集合是不会被添加任何元素的(关于这一点,CopyOnWrite算法可以保证),所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器,因此不会存在读写冲突,而写写之间的冲突则是由全局的显式锁Lock来进行防护的,因此CopyOnWrite常常被应用于读操作远远高于写操作的应用场景中。CopyOnWrite算法的基本原理如图4-20所示。

图4-20 CopyOnWrite算法的基本原理

Java中提供了两种CopyOnWrite算法的实现类,具体如下,由于使用同样比较简单,在这里我们将不做过多讲述。

▪ CopyOnWriteArrayList:在JDK1.5版本被引入,用于高并发的ArrayList解决方案,在某种程度上可以替代Collections.synchronizedList。

▪ CopyOnWriteArraySet:也是自JDK1.5版本被引入,提供了高并发的Set的解决方案,其实在底层,CopyOnWriteArraySet完全是基于CopyOnWriteArrayList实现的

虽然COW算法为解决高并发读操作提供了一种新的思路(读写分离),但是其仍然存在一些天生的缺陷,具体如下。

▪ 数组复制带来的内存开销:因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的数据集合和新拷贝的数据集合,当然旧的数据集合在拷贝结束以后会满足被回收的条件,但是在某个时间段内,内存还是会有将近一半的浪费。

▪ CopyOnWrite并不能保证实时的数据一致性:CopyOnWrite容器只能保证数据的最终一致性,并不能保证数据的实时一致性。举个例子,假设A线程修改了数据复制并且增加了一个新的元素但并未将数据集合的引用指向最新复制,与此同时,B线程是从旧的数据集合中读取元素,因此A写入的数据并不能实时地被B线程读取。

既然CopyOnWrite并不是一个很完美的高并发线程安全解决方案,那么它的应用场景又该是怎样的呢?其实我们在本节中已经提到过了,对于读操作远大于写操作,并且不要求实时数据一致性的情况,CopyOnWrite容器将是一个很合理的选择,比如在规则引擎中对新规则的引入、在告警规则中对新规则的引入、在黑白名单中对新数据的引入,并不一定需要严格保证数据的实时一致性,我们只需要确保在单位时间后的最终一致性即可,在这种情况下,我们就可以采用COW算法提高数据的读取速度及性能

链表与数据的比较:【超详细】一文学会链表解题

Map 接口

1.AbstractMap抽象类

     1.1EnumMap类(非抽象类),线程不安全。所有key必须来源于同一枚举

     1.2IdentityHashMap类 线程不安全。先根据hashCode 和相应算法找到桶位,然后依次比较每个桶位上所有元素的引用==

     1.3HashMap类 线程不安全(面试官: HashMap 为什么线程不安全?)。先根据hashCode或相应算法找到桶位,然后依次比较每个桶位上所有元素的equals。不要使用可变类型作为key。key,value可以是null HashMap原理Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析_Javadoop 面试造飞机系列:用心整理的HashMap面试题,以后都不用担心了。Java7 Java8 不太相同  JDK 1.7 和 JDK 1.8 HashMap 的组成是不同的,JDK 1.7 HashMap 的组成是数组 + 链表的形式,而 JDK 1.8 新增了红黑树的数据结构,当 HashMap 中的链表长度大于 8 时,链表结构就会转换为红黑树。(为什么不从一开始就用红黑树:应为红黑树比链表的存储空间大)。

          1.3.1LinkedHashMap类 线程不安全。使用双向链表维持元素的插入顺序

     1.4ConcurrentSkipListMap

     1.5WeakHashMap   与Guava Cache之间的差别?

2.SortedMap 接口

     2.1NavigableMap接口

          2.1.1TreeMap(也实现了AbstractMap抽象类)。线程不安全。基于红黑树算法。key判断相同标准:conpare返回0。

3.ConcurrentMap 接口  Java集合---ConcurrentHashMap原理分析 - ^_TONY_^ - 博客园原理分析 

     1.ConcurrentHashMap类(也实现了AbstractMap抽象类)ConcurrentHashMap (Java Platform SE 7 )

原理

Java 78 实现的区别Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析_Javadoop

Java 7 / 8 中的 HashMap 和 ConcurrentHashMap 全解析( 上 )

Java 7 / 8 中的 HashMap 和 ConcurrentHashMap 全解析( 下 )

hashmap的负载因子为什么是0.75:为什么 HashMap 的加载因子是0.75?

在被遍历的时候,即使是 ConcurrentHashMap 被改动,它也不会抛 ConcurrentModificationException。尽管 Iterator 的设计不是为多个线程的同时使用。

JDK1.7 设计:分离锁

在JDK1.6、1.7版本中,ConcurrentHashMap采用的是分段锁的机制(可以在确保线程安全的同时最小化锁的粒度)实现并发的更新操作,在ConcurrentHashMap中包含两个核心的静态内部类Segment和HashEntry,前者是一个实现自ReentrantLock的显式锁,每一个Segment锁对象均可用于同步每个散列映射表的若干个桶(HashBucket),后者主要用于存储映射表的键值对。与此同时,若干个HashEntry通过链表结构形成了HashBucket,而最终的ConcurrentHashMap则是由若干个(默认是16个)Segment对象数组构成的,如图4-18所示

Segment可用于实现减小锁的粒度,ConcurrentHashMap被分割成若干个Segment,在put的时候只需要锁住一个Segment即可,而get时候则干脆不加锁,而是使用volatile属性以保证被其他线程同时修改后的可见性。

JDK1.8取消了分段锁的设计,进一步减少锁冲突的发生。另外也引入红黑树的结构,进一步提高查找效率 。

在JDK 1.8版本中几乎重构了ConcurrentHashMap的内部实现,摒弃了segment的实现方式,直接用table数组存储键值对,在JDK1.6中,每个bucket中键值对的组织方式都是单向链表,查找复杂度是O(n),JDK1.8中当链表长度超过8时,链表转换为红黑树,查询复杂度可以降低到O(log n),改进了性能。利用CAS+Synchronized可以保证并发更新的安全性,底层则采用数组+链表+红黑树(提高检索效率)的存储结构,如图4-19所示。

     2.ConcurrentSkipListMap  线程安全的有序hash表。  通过跳表实现。

ConcurrentSkipListMap简介

ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上,其能够在O(log(n))时间内完成查找、插入、删除操作。调用ConcurrentSkipListMap的size时,由于多个线程可以同时对映射表进行操作,所以映射表需要遍历整个链表才能返回元素的个数,这个操作是个O(log(n))的操作。

在读取性能上,虽然ConcurrentSkipListMap不能与ConcurrentHashMap相提并论,但是ConcurrentSkipListMap存在着如下两大天生的优越性是ConcurrentSkipListMap所不具备的。

第一,由于基于跳表的数据结构,因此ConcurrentSkipListMap的key是有序的。

第二,ConcurrentSkipListMap支持更高的并发,ConcurrentSkipListMap的存取时间复杂度是O(log(n)),与线程数几乎无关,也就是说,在数据量一定的情况下,并发的线程越多,ConcurrentSkipListMap越能体现出它的优势

字典抽象类Directory

1.Hashtable 线程安全。key、value不能为null。判断同HashMap  过时了

     1.1Properties 线程安全

EnumSet EnumMap更加有效和紧凑

包装类:https://docs.oracle.com/javase/tutorial/collections/implementations/wrapper.html

1.同步包装

The synchronization wrappers add automatic synchronization (thread-safety) to an arbitrary collection. Each of the six core collection interfaces — CollectionSetListMap,SortedSet, and SortedMap — has one static factory method.

public static <T> Collection<T> synchronizedCollection(Collection<T> c);

public static <T> Set<T> synchronizedSet(Set<T> s);

public static <T> List<T> synchronizedList(List<T> list);

public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);

public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);

public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);

但是在遍历的时候还是需要synchronize 下

2.不可变包装

public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c);

public static <T> Set<T> unmodifiableSet(Set<? extends T> s);

public static <T> List<T> unmodifiableList(List<? extends T> list);

public static <K,V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m);

public static <T> SortedSet<T> unmodifiableSortedSet(SortedSet<? extends T> s);

public static <K,V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m);

抽象类

The following list summarizes the abstract implementations:

  • AbstractCollection — a Collection that is neither a Set nor a List. At a minimum, you must provide the iterator and the size methods.

  • AbstractSet — a Set; use is identical to AbstractCollection.

  • AbstractList — a List backed up by a random-access data store, such as an array. At a minimum, you must provide the positional access methods (get and, optionally, set, remove, and add) and the size method. The abstract class takes care of listIterator (and iterator).

  • AbstractSequentialList — a List backed up by a sequential-access data store, such as a linked list. At a minimum, you must provide the listIterator and size methods. The abstract class takes care of the positional access methods. (This is the opposite of AbstractList.)

  • AbstractQueue — at a minimum, you must provide the offer, peek, poll, and size methods and an iterator supporting remove.

  • AbstractMap — a Map. At a minimum you must provide the entrySet view. This is typically implemented with the AbstractSet class. If the Map is modifiable, you must also provide the put method.

不安全的容器迭代时会可能会抛出ConcurrentModificationException(如果有并发的修改发生)

Comparable 接口

只有一个方法  int compareTo(T other)

Comparator 接口

只有一个方法 int compare(T o1,T o2)

Enumeration 接口  过时了

Enumeration (Java Platform SE 7 )

子类 StringTokenizer

Iterator Iterator (Java Platform SE 8 )

The remove method removes the last element that was returned by next from the underlying Collection. The remove method may be called only once per call to next and throws an exception if this rule is violated.

ListIterator  ListIterator (Java Platform SE 8 )

Iterator 与ListIterator 区别:Iterator 只能单向遍历。ListIterator可以双向遍历并修改。

List和Set都有iterator()来取得其迭代器。对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的,Iterator和ListIterator主要区别在以下方面:

  • ListIterator有add()方法,可以向List中添加对象,而Iterator不能

  • ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

  • ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

  • 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。

因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。

聚合操作 https://docs.oracle.com/javase/tutorial/collections/streams/index.html

HashMap JDK7 JDK8实现原理不同

Java 7 HashMap的put方法:

在JDK 7中,链表存储有一个缺点,就是当数据很多的时候,链表就会很长,每次查询都会遍历很长的链表。

因此在JDK 8中为了优化HashMap的查询效率,将内部的结构改为数组+链表+和红黑树,当一个哈希桶后面的链表长度>8的时候,就会将链表转化为红黑树,红黑树是二分查找,提高了查询的效率。

Java8 HashMap的put方法

1.8中,数组有两种情况会发生扩容:

  1. 一是超过阈值

  2. 二是链表转为红黑树且数组元素小于64时

JDK 1.7的扩容条件是数组长度大于阈值且存在哈希冲突.

JDK 1.8扩容条件是数组长度大于阈值或链表转为红黑树且数组元素小于64

HashMap的一连串问题

让我再撸一次HashMap

HashMap在什么条件下扩容?

此题可以组成如下连环炮来问

  • HashMap在什么条件下扩容?

  • 为什么扩容是2的n次幂?

  • 为什么为什么要先高16位异或低16位再取模运算?

HashMap在什么条件下扩容?

如果bucket满了(超过load factor*current capacity),就要resize。

load factor为0.75,为了最大程度避免哈希冲突

current capacity为当前数组大小。

为什么扩容是2的次幂?

HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length。

但是,大家都知道这种运算不如位移运算快。

因此,源码中做了优化hash&(length-1)。

也就是说hash%length==hash&(length-1)

那为什么是2的n次方呢?

因为2的n次方实际就是1后面n个0,2的n次方-1,实际就是n个1。

例如长度为8时候,3&(8-1)=3  2&(8-1)=2 ,不同位置上,不碰撞。

而长度为5的时候,3&(5-1)=0  2&(5-1)=0,都在0上,出现碰撞了。

所以,保证容积是2的n次方,是为了保证在做(length-1)的时候,每一位都能&1  ,也就是和1111……1111111进行与运算。

为什么为什么要先高16位异或低16位再取模运算?

我先晒一下,jdk1.8里的hash方法。1.7的比较复杂,咱就不看了。

hashmap这么做,只是为了降低hash冲突的几率。

打个比方,当我们的length为16的时候,哈希码(字符串“abcabcabcabcabc”的key对应的哈希码)对(16-1)与操作,对于多个key生成的hashCode,只要哈希码的后4位为0,不论不论高位怎么变化,最终的结果均为0。

如下图所示

而加上高16位异或低16位的“扰动函数”后,结果如下

可以看到: 扰动函数优化前:1954974080 % 16 = 1954974080 & (16 - 1) = 0 扰动函数优化后:1955003654 % 16 = 1955003654 & (16 - 1) = 6 很显然,减少了碰撞的几率。

Fail-Fast

很多集合类都提供了一种 fail-fast 机制,因为大部分集合内部都是使用 Iterator 进行遍历,在循环中使用同步锁的开销会很大,而 Iterator 的创建是轻量级的,所以在集合内部如果有并发修改的操作,集合会进行快速失败,也就是 fail-fast。当他们发现容器在迭代过程中被修改时,会抛出 ConcurrentModificationException 异常,这种快速失败不是一种完备的处理机制,而只是 善意的捕获并发错误。

如果查看过 ConcurrentModificationException 的注解,你会发现,ConcurrentModificationException 抛出的原则由两种,如下

造成这种异常的原因是由于多个线程在遍历集合的同时对集合类内部进行了修改,这也就是 fail-fast 机制。

该注解还声明了另外一种方式

fail-safe

fail-safe 是 Java 中的一种 安全失败 机制,它表示的是在遍历时不是直接在原集合上进行访问,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 ConcurrentModificationException。java.util.concurrent 包下的容器都是安全失败的,可以在多线程条件下使用,并发修改。

Deque 是一个双端队列,分别实现了在队列头和队列尾的插入。Deque 的实现有 ArrayDeque、ConcurrentLinkedDeque,BlockingDeque 的实现有 LinkedBlockingDeque 。

阻塞模式一般用于生产者 - 消费者队列,而双端队列适用于工作密取。在工作密取的设计中,每个消费者都有各自的双端队列,如果一个消费者完成了自己双端队列的任务,就会去其他双端队列的末尾进行消费。密取方式要比传统的生产者 - 消费者队列具有更高的可伸缩性,这是因为每个工作密取的工作者都有自己的双端队列,不存在竞争的情况。

ArrayDeque

ArrayDeque 是 Deque 的可动态调整大小的数组实现,其内部没有容量限制,他们会根据需要进行增长。ArrayDeque 不是线程安全的,如果没有外部加锁的情况下,不支持多线程访问。ArrayDeque 禁止空元素,这个类作为栈使用时要比 Stack 快,作为 queue 使用时要比 LinkedList 快。

除了 remove、removeFirstOccurrence、removeLastOccurrence、contains、interator.remove 外,大部分的 ArrayDeque 都以恒定的开销运行。

注意:ArrayDeque 是 fail-fast 的,如果创建了迭代器之后,却使用了迭代器外部的 remove 等修改方法,那么这个类将会抛出 ConcurrentModificationException 异常。

ConcurrentLinkedDeque

ConcurrentLinkedDeque 是 JDK1.7 引入的双向链表的无界并发队列。它与 ConcurrentLinkedQueue 的区别是 ConcurrentLinkedDeque 同时支持 FIFO 和 FILO 两种操作方式,即可以从队列的头和尾同时操作(插入/删除)。ConcurrentLinkedDeque 也支持 happen-before 原则。ConcurrentLinkedDeque 不允许空元素。

LinkedBlockingDeque

LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。LinkedBlockingDeque 把初始容量和构造函数绑定,这样能够有效过度拓展。初始容量如果没有指定,就取的是 Integer.MAX_VALUE,这也是 LinkedBlockingDeque 的默认构造函数。

Java高并发编程详解:深入理解并发核心库

并发队列在使用中需要注意的问题

虽然并发队列在高并发多线程的环境中有着优异的性能表现,但是如果对其使用不当不仅对性能没有任何提升反倒会降低整个系统的运行效率。

1. 在并发队列中使用size方法不是个好主意

我们知道每一个Collection(队列Queue也是Collection的子接口)都提供了size()方法用于获取Collection中的元素个数,但是在并发队列中执行该方法却不是一个明智的操作,为什么呢?

▪ 首先,并发队列是基于链表的结构实现的,并且在其内部并未提供类似于计数器的变量(当元素插入队列计数器时增一,当元素从队列头部被移除时计数器减一),因此想要获得当前队列的元素个数,需要遍历整个队列才能计算得出(效率低下)。

▪ 其次并发队列采用无锁(Lock-Free)的算法实现,因此在某个线程中执行size()方法获取元素数量的同时,其他线程也可以对该队列进行读写操作,所以size()返回的数值不会是一个精确值,而是一个近似值、一个估计值。

在JDK官网的帮助文档中对该方法在使用上也有详细的描述“Returns the number of elements in this queue. If this queue contains more than Integer. MAX_VALUE elements, returns Integer.MAX_VALUE.

Beware that, unlike in most collections, this method is NOT a constant-time operation. Because of the asynchronous nature of these queues, determining the current number of elements requires an O(n) traversal. Additionally, if elements are added or removed during execution of thismethod, the returned result may be inaccurate. Thus, this method is typically not very useful in concurrent applications.”

2. ConcurrentLinkedQueue的内存泄漏问题

另外,ConcurrentLinkedQueue在执行remove方法删除元素时还会出现性能越来越低,甚至内存泄漏的问题。这个问题最早是由Jetty的开发者发现的,因为Jetty内部的线程池采用的就是ConcurrentLinkedQueue作为任务的队列,随后在很多开源项目中都发现了内存泄漏的问题,比如ApacheCassandra。

▪ JDK BUG地址:https:// bugs.java.com/bugdatabase/view_bug.do?bug_id=8137185

▪ Jetty BUG地址:https:// bugs.eclipse.org/bugs/show_bug.cgi?id=477817

▪ Cassandra BUG地址:https:// issues.apache.org/jira/browse/CASSANDRA-9549

值得庆幸的是,在Jetty中该问题被发现后得到了解决,开发者们采用ConcurrentHashSet替代了ConcurrentLinkedQueue的解决方案,不过很遗憾的是,在JDK的7、8、9版本中该问题依然存在(笔者亲测)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值