Java 中有许多的集合,常用的有ListSetQueueMap。 其中 List,Set,Queue都是Collection(集合),List<String>中<>的内容表示其中元素的类型,是泛型的一种使用。不能直接使用简单数据类型做泛型的原因:集合类(比如Set)在进行各种 "操作" ( 如contains()) 时都会调用元素本身提供的 "方法" ( 如hashCode() 和 equals()),而不是由集合类自身去实现这些 "方法"。这就要求如果某人想要用这个集合执行某些 "操作",那就必须在要加入集合的元素中实现相应的 "方法"。

fail-fast 机制是 java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast事件。例如:当某一个线程A通过 iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生 fail-fast事件。

一、ArrayList 不安全阐述


List(列表)相当于数组。长度可变,元素存放有一定的顺序,下标从0开始。在JDK中,List作为接口,本身已经声明好了所有的方法(比如add(), contains()......),所以不管是选择 ArrayList还是 LinkedList,完成各种操作的时候依然是使用 List中已经声明过的这一套方法,对使用者来说没有区别。二者只是内部实现逻辑不同,所以在不同的应用场景下会有不同的效率。

【1】ArrayList<> 底层通过数组实现数据的存储。初始的大小为10,超过默认值时,通过 Arrays 进行扩容,如下:允许存空元素,有专门保存容量capacity属性

1 //elementData 需要扩容的数组对象 , newCapacity 扩容的大小 int 类型
2 elementData = Arrays.copyOf(elementData, newCapacity);

【2】ArrayList 线程不安全的例子如下:

 1 public class Test {
2 public static void main(String[] args) {
3 List<String> list = new ArrayList<>();
4 for (int i=1;i<3000;i++){
5 new Thread(){
6 @Override
7 public void run(){
8 list.add(UUID.randomUUID().toString().substring(0,7));
9 System.out.println(list);
10 }
11 }.start();
12 }
13 }
14 }

【3】故障现象:java.util.ConcurrentModificationException

【4】导致原因:因并发无锁导致数据修改异常。
【5】解决方案:① Vector 是线程安全的,可以解决上面的问题。但是性能会急剧下降(不建议使用)。
   ② 使用Collections工具类 Collections.synchronizedList(new ArrayList<>()); 解决上述问题。
   ③ new CopyOnWriteArrayList<>():写时复制,CopeOnWrite 容器既写时复制的容器。往一个容器添加元素的时候,不直接往当前容器 Object[] 添加,而是先将当前容器 object[] 进行 copy,复制出一个新的容器 Object[] newElements,然后往新的容器中添加元素,添加完元素之后,再将原容器的引用指向新容器 setArray(newElements); 这样做的好处是可以对 CopeOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以 CopeOnWrite 容器也是一种读写分离的思想,读和写不同的容器。底层源码如下:

 1 public boolean add(E e) {
2 final ReentrantLock lock = this.lock;
3 lock.lock();
4 try {
5 Object[] elements = getArray();
6 int len = elements.length;
7 Object[] newElements = Arrays.copyOf(elements, len + 1);
8 newElements[len] = e;
9 setArray(newElements);
10 return true;
11 } finally {
12 lock.unlock();
13 }
14 }

扩展一:Arrays.asList 遇到的问题


使用Arrays.asList()方法时把一个数组转化成 List列表,对得到的 List列表进行 add()和 remove()操作, 会导致 java.lang.UnsupportedOperationException异常。

【1】查看 Arrays.asList 源码

1 public static <T> List<T> asList(T... a) {
2 return new ArrayList<>(a);
3 }

【2】查看此 ArrayList结构:add 和 remove 方法继承自 AbstractList

1 private static class ArrayList<E> extends AbstractList<E> {
2 ArrayList(E[] array) {
3 a = Objects.requireNonNull(array);
4 }
5 }

【3】在查看 AbstractList结构:add 和 remove 方法直接返回 UnSupportedOperationException

 1 public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
2
3 public boolean add(E e) {
4 add(size(), e);
5 return true;
6 }
7
8 public void add(int index, E element) {
9 throw new UnsupportedOperationException();
10 }
11
12 public E remove(int index) {
13 throw new UnsupportedOperationException();
14 }
15 }

所以说 Arrays.asList 返回的 List 是一个不可变长度的列表,此列表不再具备原 List 的很多特性,因此慎用 Arrays.asList 方法。

下面代码输出是什么?

1 public static void main(String[] args) {
2 int[] data = {1,2,3,4};
3 List list = Arrays.asList(data);
4 System.out.println(list.size());
5 }

由上面 asList 源码我们可以看到返回的 Arrays的内部类 ArrayList 构造方法接收的是一个类型为 T 的数组,而基本类型是不能作为泛型参数的,所以这里参数 a只能接收引用类型,自然为了编译通过编译器就把上面的 int[] 数组当做了一个引用参数,所以 size 为 1,要想修改这个问题很简单,将 int[] 换成 Integer[] 即可。所以原始类型不能作为 Arrays.asList 方法的参数,否则会被当做一个参数。

扩展二:ArrayList 的 subList的注意事项


《阿里巴巴Java开发手册》泰山版中是这样描述的:

使用起来很简单,也很好理解,不过还是有以下几点要注意,否则会造成程序错误或者异常:
【1】修改原集合元素的值,会影响子集合;
【2】修改原集合的结构,会引起 ConcurrentModificationException异常;
【3】修改子集合元素的值,会影响原集合;
【4】修改子集合的结构,会影响原集合;

看下它的源码:

1 public List<E> subList(int fromIndex, int toIndex) {
2 subListRangeCheck(fromIndex, toIndex, size);
3 return new SubList(this, 0, fromIndex, toIndex);
4 }

可以看到,它调用了 SubList类的构造函数,该构造函数的源码如下图所示:

 1 public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
3 ......
4 private class SubList extends AbstractList<E> implements RandomAccess {
5 private final AbstractList<E> parent;
6 private final int parentOffset;
7 private final int offset;
8 int size;
9
10 SubList(AbstractList<E> parent,
11 int offset, int fromIndex, int toIndex) {
12 this.parent = parent;
13 this.parentOffset = fromIndex;
14 this.offset = offset + fromIndex;
15 this.size = toIndex - fromIndex;
16 this.modCount = ArrayList.this.modCount;
17 }
18 ......

可以看出,SubList类是 ArrayList的内部类,该构造函数中也并没有重新创建一个新的 ArrayList,所以修改原集合或者子集合的元素的值,是会相互影响的。

二、Set 不安全阐述


Set 与 List 是相同的,都是线程不安全的,都会出现 ConcurrentModificationException 异常,解决办法常见的有两种:
【1】Collections.synchronizedSet(new HashSet<>()); 通过工具类中的同步代码块可以解决此问题,但性能会受影响。
【2】new CopyOnWriteArraySet<>() 和 List 相同,通过写时复制即可高效解决此问题。底层通过 CopyOnWriteArrayList 实现:

1 public CopyOnWriteArraySet() {
2 al = new CopyOnWriteArrayList<E>();
3 }

扩展:hashSet 的底层是怎么实现的:底层其实是一个 hashMap,源代码如下:

1 public HashSet() {
2 map = new HashMap<>();
3 }

我们的疑惑是,Map 不应该存放的是两个值么,而 Set 存储的都是一个值呀,其实是因为 Map 中的 Key 与 Set 具有相同的特性。因此 Set 的值都存储在 Map 中的 key 中,而 value 存储一个固定的 Object 常量。源代码如下:

1 //存储的 value 值
2 private static final Object PRESENT = new Object();
3 public boolean add(E e) {
4 return map.put(e, PRESENT)==null;
5 }

三、Map 不安全阐述


Map 与 List/Set 是相同的,都是线程不安全的,都会出现 ConcurrentModificationException 异常,解决办法常见的有三种:
【1】Collections.synchronizedMap(new HashMap<>()); 通过工具类中的同步代码块可以解决此问题,但性能会受影响。
【2】HashTable: HashTable 容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。
【3】new ConcurrentHashMap<>(); 推荐使用,此方法创建的 Map 是线程安全的。而 JDK1.7 之前的 ConcurrentHashMap 使用分段锁机制实现,JDK1.8 则使用数组+链表+红黑树数据结构和CAS原子操作实现 ConcurrentHashMap;
具体可以查看:连接

Java面试——不安全的集合类的更多相关文章

  1. 转:最近5年133个Java面试问题列表

    最近5年133个Java面试问题列表 Java 面试随着时间的改变而改变.在过去的日子里,当你知道 String 和 StringBuilder 的区别就能让你直接进入第二轮面试,但是现在问题变得越来 ...

  2. JAVA面试精选【Java基础第一部分】

    这个系列面试题主要目的是帮助你拿轻松到offer,同时还能开个好价钱.只要能够搞明白这个系列的绝大多数题目,在面试过程中,你就能轻轻松松的把面试官给忽悠了.对于那些正打算找工作JAVA软件开发工作的童 ...

  3. java面试和笔试大全 分类: 面试 2015-07-10 22:07 10人阅读 评论(0) 收藏

    2.String是最基本的数据类型吗? 基本数据类型包括byte.int.char.long.float.double.boolean和short. java.lang.String类是final类型 ...

  4. 近5年133个Java面试问题列表

    Java 面试随着时间的改变而改变.在过去的日子里,当你知道 String 和 StringBuilder 的区别就能让你直接进入第二轮面试,但是现在问题变得越来越高级,面试官问的问题也更深入. 在我 ...

  5. java 面试

        115个Java面试题和答案——终极列表(上) 本文我们将要讨论Java面试中的各种不同类型的面试题,它们可以让雇主测试应聘者的Java和通用的面向对象编程的能力.下面的章节分为上下两篇,第一 ...

  6. JAVA面试精选

    JAVA面试精选[Java基础第一部分] 这个系列面试题主要目的是帮助你拿轻松到offer,同时还能开个好价钱.只要能够搞明白这个系列的绝大多数题目,在面试过程中,你就能轻轻松松的把面试官给忽悠了.对 ...

  7. java面试笔试大汇总

    java面试笔试题大汇总5 JAVA相关基础知识 1.面向对象的特征有哪些方面 1.抽象:2.继承:3.封装:4. 多态性: 2.String是最基本的数据类型吗? 基本数据类型包括byte.int. ...

  8. Java面试宝典

    相关概念 面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消 ...

  9. Java 面试宝典-2017

    http://www.cnblogs.com/nelson-hu/p/7190163.html Java面试宝典-2017   Java面试宝典2017版 一. Java基础部分........... ...

  10. Java面试宝典-2017

    Java面试宝典2017版 一. Java基础部分........................................................................... ...

随机推荐

  1. Windows使用cmd命令查看当前用户名

    https://blog.csdn.net/qq_46068659/article/details/106037024 查看用户: net user 查看当前用户: whoami

  2. 通过cmd对数据库SQL进行创建表空间并创建用户

    通过cmd对数据库SQL进行创建表空间并创建用户 1.创建表空间:create tablespace stx(名字) datafile ' 目录路径\stx(名字).dbf ' size 64m; 2 ...

  3. vue组件淡入浅出动画

    点击动画 hello hello world! .test-enter, .test-leave-to { opacity: 0 } .test-enter-to, .test-leave { opa ...

  4. APP性能测试——安装耗时测试

    安装耗时: 这里我们用pm命令安装app,来截取安装时间(不要使用adb install安装,因为那样多一个push app的耗时). 示例代码: import os,time,datetime de ...

  5. Linux模拟手动输入指令

    当执行一个命令,其中会询问你的选择的时候,在脚本上,可以如此模拟手动输入: 运行的命令 <<EOF 键盘输入 EOF 例如挂载硬盘: DISK=/dev/sdb /sbin/fdisk $ ...

  6. Hadoop编程——Java编写MapReduce:WordCount案例

    一.MapReduce简介 MapReduce是一种面向大数据平台的分布式并行计算框架,它允许使用人员在不会分布式并行编程的情况下,将程序运行在分布式系统上.它提供的并行计算框架,能自动完成计算任务的 ...

  7. ORA-00972: identifier is too long异常处理

    环境:由于数据库更换,做数据同步,提示 too long 问题,导致一直无法同步完数据. 经排查 oracle 历史数据库版本: Oracle Database 12c Standard Editio ...

  8. 2.21(html)

  9. 网易面经-hashmap是否能存null(debug源码)

    面试时一定要坚持自己的认知,不要让面试官两三下pua了. 结果是可以的 null作为key时被放在了tab下标为0的位置,只能有一个null null作为value时不受限制 虽然说value为nul ...

  10. Linux 中最常用 150 个命令汇总