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. 删除Windows10资源管理器多余的入口

    ///// 删除3D对象 定位到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\Nam ...

  2. IQueryable 和 IEnumerable 的区别

    讲一讲 IQueryable 和 IEnumerable 的区别. 我们会在使用 LINQ 查询方法之后,又使用 ToList 等方法,将查询结果转换成集合. 如果我们不使用 ToList 呢? 比如 ...

  3. elementUI table 数组中各项加逗号隔开

    js处理 //this.newTableData 数组 this.newTableData.map((item,index)=>{ //item.label 数组 if(item.label){ ...

  4. javase我的笔记

    学习java编程 |--需要遵循其语法规范 |--在其开发环境下编程 准备开发java环境 |--安装jdk |--配置jdk jdk:含义是java提供的一套开发工具,包含jre\编译器等 |--编 ...

  5. 浙大版《C语言程序设计(第3版)》题目集 练习3-3 统计学生平均成绩与及格人数 (15 分)

    练习3-3 统计学生平均成绩与及格人数 (15 分) 本题要求编写程序,计算学生们的平均成绩,并统计及格(成绩不低于60分)的人数.题目保证输入与输出均在整型范围内. 输入格式: 输入在第一行中给出非 ...

  6. AX2012 快速清空整个log表数据

    如果当一个log表的数据非常大的时又需要清理时,如果允许删除全部数据,在AX里,可以 将log表的TableType调整为[TempDB], 保存同步后再将TableType设置回[Regular]即 ...

  7. SQLite 帮助类

    public static class SqliteHelper { /// <summary> /// 获得连接对象 /// </summary> /// <retur ...

  8. OO课程第一阶段(前三次作业)总结Blog1

    OO课程第一阶段(前三次作业)总结Blog1 前言:学习OOP课程的第一阶段已经结束了,在此进行对于知识点,题量,难度的个人看法. (1)相比于另外两次作业,第一次作业基本上是基本的编程的语法训练,题 ...

  9. Tomcat和Maven安装与配置

    链接:https://pan.baidu.com/s/1aezz2pfCn0DCCPw8udQFXA 提取码:wd4f 一.网站发布1.1.为什么要用tomcat网页开发好了,该如何发布呢?我们需要一 ...

  10. java实现AES/CBC/pack5padding加解密算法

    最近要测试一个借口,借口的传值参数是使用AES加密,偏移量为0000000000000000,秘钥:12345678901234567890123456789012,加密后内容转成16进制发送,用网上 ...