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. Spring学习记事本

    原因:原因:Application的启动类不能放在默认的java目录,必须放在建有包的目录下.

  2. 快学起来!python入门自学必看

    记得刚开始学python的时候,各种买书各种找资料,最后发现资料找了一大堆,但都是东一块西一块,内容不全且不系统,无意间发现这个宝藏网站,真的是太全了,当作工具书,时不时的翻翻,总会发现一些惊喜.0基 ...

  3. python实现学生信息管理系统——界面版

    前言(写于2022年5月17日) 这篇博客是我两年前大二时写的,一直放在草稿箱里,还没写完,现在快毕业了,突然翻出来了.虽然博客没写完,但早就写好了代码,可运行的exe文件以及文档.现在上传到gite ...

  4. tensorflow2.0和之前版本读取模型的接口变化

    tensorflow2.0 c++加载模型可以用接口LoadSavedModel: SessionOptions opts;     tensorflow::RunOptions run_option ...

  5. Vue.sync修饰符与this.$emit('update:xxx', newXXX)

    Vue通过prop进行双向数据绑定.子组件数据变化,一般只能通过 this.$emit(func, val) 回调父组件函数来传值给父组件. Vue2.3版本引入sync,作为一个事件绑定语法糖,当子 ...

  6. Shell 更多结构化命令(流程控制)

    更多的结构化命令 上一章里,你看到了如何通过检查命令的输出和变量的值来改变 shell 脚本程序的流程.本章会继续介绍能够控制 shell 脚本流程的结构化命令.你会了解如何重复一些过程和命令,也就是 ...

  7. Python爬虫之用Selenium做爬虫

    我们在用python做爬虫的时候,除了直接用requests的架构,还有Scrapy.Selenium等方式可以使用,那么今天我们就来聊一聊使用Selenium如何实现爬虫. Selenium是什么? ...

  8. git在linux安装步骤详解!!

    linux上安装,以centos 7.x为例 yum命令安装 yum install gityum install 安装的git不是最新版本,如需最新版本需要自行编译 到下面的网站下载合适的版本 ht ...

  9. requests模块获取cookie -----class 'requests.cookies.RequestsCookieJar'

    #coding=utf-8 import requests url="http://www.baidu.com" response=requests.get(url) cookie ...

  10. String类型时间与Date时间转换

    1. String类型的时间转为DateTime public static Date transferString2Date(String s) { Date date = new Date(); ...