精华内容
下载资源
问答
  • 线程安全ArrayList

    2021-02-25 10:33:18
      Java中ArrayList是线程不安全的,所以在多线程操作ArrayList时,需要确保其线程的安全性,有以下几种方法: a Collections.synchronizedList   最常用的方法是通过 Collections 的 synchronizedList 方法将 ...

      Java中ArrayList是线程不安全的,所以在多线程操作ArrayList时,需要确保其线程的安全性,有以下几种方法:

    a Collections.synchronizedList

      最常用的方法是通过 Collections 的 synchronizedList 方法将 ArrayList 转换成线程安全的容器后再使用。

    List list =Collections.synchronizedList(new ArrayList); 
    

    b 为list.add()方法加锁

    synchronized(list.get()) {
     	list.get().add(model)
    }; 
    

    c CopyOnWriteArrayList

      使用线程安全的 CopyOnWriteArrayList 代替线程不安全的 ArrayList。

    List list1 = new CopyOnWriteArrayList();
    

    d 使用ThreadLocal

      使用ThreadLocal变量确保线程封闭性(封闭线程往往是比较安全的, 但由于使用ThreadLocal封装变量,相当于把变量丢进执行线程中去,每new一个新的线程,变量也会new一次,一定程度上会造成性能[内存]损耗,但其执行完毕就销毁的机制使得ThreadLocal变成比较优化的并发解决方案)。

    ThreadLocal> threadList = new ThreadLocal<>() {
    	@Override protected List initialValue() { 
    		return new ArrayList(); 
    	} 
    };
    

    注意:

      在JDK中,获取线程安全的List,我们可以使用Collections.synchronizedList(List list)方式,也可以使用CopyOnWriteArrayList类。在真实环境中,使用它们可以根据我们的业务需要,在插入操作远远超过读取时,建议使用第一种方式,这是因为CopyOnWriteArrayList在插入的过程中会创建新的数组,这样在数据量特别大的情况下,对内存的消耗是很大的。当然,如果是读取操作远远大于插入时,第二种方式肯定更占优势,毕竟读取操作完全不需要加锁。

    展开全文
  • ArrayListArrayList底层为一个Object类型的数组,初始大小为10,在多线程操作ArrayList时会产生各种各样的线程不安全问题,该文章首先还原问题,用三种方法解决该问题。 问题还原 public class ArrayListExample...

    ArrayList:ArrayList底层为一个Object类型的数组,初始大小为10,在多线程操作ArrayList时会产生各种各样的线程不安全问题,该文章首先还原问题,用三种方法解决该问题。

    • 问题还原
    public class ArrayListExample {
        public static void main(String[] args) {
            List list = new ArrayList();
            //List list = Collections.synchronizedList(new ArrayList<>());
            for(int i = 1 ;i<=30;i++){
                new Thread(() ->{
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }

    可以看到使用ArrayList所导致的问题如上所示

    • 问题解决

    1.使用Vector代替,但是Vector在java中的出现比ArrayList出现的还要早,导致性能下降,一般不使用这种方式

    2.使用Collections.synchronizedList(new ArrayList<>())解决该问题,解决方式如下

    public class ArrayListExample {
        public static void main(String[] args) {
            //List list = new ArrayList();
            List list = Collections.synchronizedList(new ArrayList<>());
            for(int i = 1 ;i<=30;i++){
                new Thread(() ->{
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }

    3.使用CopyOnWriteArrayList,读写分离,写入时复制

    public class ArrayListExample {
        public static void main(String[] args) {
            List list = new CopyOnWriteArrayList();
            //List list = Collections.synchronizedList(new ArrayList<>());
            for(int i = 1 ;i<=30;i++){
                new Thread(() ->{
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }

    以上三种方式问题都可以解决,推荐第二种或者第三种。

     

     

     

    展开全文
  • 我们已经知道多线程下会有各种不安全的问题,都知道并发...一、List 的不安全1.1 问题看一段代码:public static void main(String[] args) {ArrayList list = new ArrayList<>();for (int i = 0; i < 3; i...

    我们已经知道多线程下会有各种不安全的问题,都知道并发的基本解决方案,这里对出现错误的情况进行一个实际模拟,以此能够联想到具体的生产环境中。

    一、List 的不安全

    1.1 问题

    看一段代码:

    public static void main(String[] args) {

    ArrayList list = new ArrayList<>();

    for (int i = 0; i < 3; i++){

    new Thread(()->{

    list.add(UUID.randomUUID().toString().substring(0,8));

    System.out.println(list);

    },String.valueOf(i)).start();

    }

    }

    过程很简单,只有 3 个线程而已,对同一个 list 进行 add 的写操作,并随后进行输出的读操作。

    输出结果,多执行几次,惊喜多多。

    9bfa1f1b75be95940144019a59751ace.png

    那么,情况不严重的时候,这里显然还正常运行结束了,只是导致了还没来得及写的时候,就已经读出了数据。

    如果把线程数增加试试,可能还会看到这样的奇观:

    75fd66aacf3c1a3dc695024ea87c10d5.png

    报错了:重点异常:java.util.ConcurrentModificationException,翻译过来就是并发修改异常。

    1.2 产生原因

    普通的 ArrayList 集合里面没有任何特殊处理,在多线程情况下,他们可以共同进行访问。

    那么在多线程同时操作的时候,按照操作的情况就有这几种:

    各个线程都读。不影响,前提是只有读;

    各个线程都写。会出现问题,这里的点有两种情况:

    值覆盖问题,因为 ArrayList 的底层数组,写入值的时候要先计算到一个下标位置,然后给对应的位置去赋值,多线程就会出现值覆盖的问题;

    空指针异常,因为 ArrayList 的底层数组,写入值在数组满的时候需要扩容,在扩容还没完成的时候,新的下标却已经计算出来并且要去插入,那么就会出现空指针异常。

    有的读有的写。那么显然对于多个线程来说,2 里面各个线程写的情况对应的问题就会出现。除此之外:

    如果多线程有的读有的写,对于 ArrayList 底层,某些情况下,对象是不允许进行修改的,如果修改了,后面调用某些方法时,就会检测到,然后就直接抛出ConcurrentModificationException。

    具体一下,因为源码里,写操作对集合修改是写,而next、remove等 Itr 的遍历读操作的时候会通过当前集合的修改次数与 Itr 对象创建时记录的次数校验集合是否被修改,如果修改了,不一致就说明正读的时候还有别的线程在改,就会抛出异常。

    JDK作者说了,会抛这个异常的都叫fail-fast iterator。

    第 3 种情况就是对应了我们上面的代码在线程多起来的情况,因为输出 list 的时候需要遍历的读,而此时还有别的线程在进行 add 的修改操作。

    1.3 解决方法

    注意:当然不能自己加锁,因为集合类已经再演变过程有线程安全的替代品,自己的代码加锁的粒度已经在集合的外层再加一层了,粒度太大。

    同样能够完成 ArrayList 功能的,可以使用 Vector,查看源码就会发现,Vector 的基本结构是一个叫 elementData 的 Object 类型的数组,和 ArrayList 类似,但是对应的操作方法,基本都加上了 synchronized 关键字,因此它是线程安全的集合。

    数据量小的时候,使用 Collections.synchronizedList(new ArrayList())这种方式,来包裹这个集合,跟 Collections 里面 synchronizedMap包裹hashmap 是一样的,更多的,还有:

    06f7a79e370f3d7a179c36c62ef53de9.png

    显然能传入参数的这些基本集合类都是线程不安全的。

    第三种就是,直接使用 juc 包里面的,CopyOnWriteArrayList() 类,这个类就是并发包给我们提供的线程安全的列表类。1.4里介绍了这个集合。

    1.4 CopyOnWriteArrayList

    对于 CopyOnWriteArrayList 类,名字上就可以听的出来,写时复制的列表。

    首先,按照前面的我们的分析,只要涉及了写的操作,和读或者写搭配的多线程情况,就会出现问题,那么多线程同时读却不会出现问题,因此相比较于直接都加上 synchronized 的方式,他的思想就是:读写分离。这个思想在数据库对于高并发的架构层面也有一样的设计。

    这样一来,对于这个 List 集合来说,分为不同操作的保证线程安全的策略,就能够保证更好的性能。

    写的方法,我们首先可以看 add 方法源码:

    29c86163ddfceec11bbb66e535efaf44.png

    步骤很清楚,如果有了写操作,需要加锁:

    加锁

    获取到当前的集合数组;

    计算长度;

    调用 Arrays.copyOf 方法进行添加操作,每次只添加一个元素进去;

    修改引用,更新最新的集合;

    return true。

    解锁

    其中的 lock 在源码里就是一个:

    c0f518c8a5db2634644a5f6022c336a6.png

    可以看到是一个普通的 Object。

    那么加锁的时候就用 synchronized 对 Object 进行加锁,没有采用 juc 的 ReetrantLock,注释li也写了,偏向于使用内置的 monitor 也就是 synchronized 底层 monitor 锁,这一点也充分说明了 synchronized 的性能更新使得源码作者使用它。

    这个方法是处理最直接的,其他对应的写操作:remove、set等等也是一样的基础流程。

    我们再来看看读操作 get 方法:

    e6f5f413fa2e26d686afce4bd9fec4d1.png

    二、HashSet 的不安全

    2.1 问题及原因

    我们还是用 List 一样的测试代码;

    public class TestSet {

    public static void main(String[] args) {

    HashSet set = new HashSet<>();

    for (int i = 0; i < 100; i++){

    new Thread(()->{

    set.add(UUID.randomUUID().toString().substring(0,8));

    System.out.println(set);

    },String.valueOf(i)).start();

    }

    }

    }

    就会看到一样的错误:

    90b29f6ed6509d9e63c0b283e05b5ea9.png

    2.2 出现问题的原因

    其实从出现 ConcurrentModificationException 异常来看,我们可以猜测是和 List 类似的原因导致的异常。

    可以看到,源码里面,Set 的底层维护的是一个 HashMap 来实现。对于遍历操作来说,都是一样的使用了 fail-fast iterator 迭代器,因此会出现这个异常。

    另外,因为 HashSet 的底层是 HashMap ,本质上,对于每一个 key ,保证唯一,使用了一个 value 为 PRESENT 常量的键值对进行存储。

    17f98f887e0ce5bdec76cae23ece6a61.png

    put 的过程也是调用 map 的 put 方法。

    2.3 解决方案

    List 有对应的 Vector 可用,本来就是线程安全的集合,但是 Set 没有;

    数据量小的时候,使用 Collections.synchronizedSet(new HashSet<>()) 这种方式,来包裹这个集合,上面我们使用 List 的时候也有类似的方法;

    同样的,juc包为我们提供了新的线程安全集合 CopyOnWriteArraySet()。

    2.4 CopyOnWriteArraySet

    按照前面的思路,List 的对应线程安全集合是在 List 集合的数组基础上进行加锁的相关操作。

    那么 Set 既然底层是 HashMap,对应的线程安全集合就应该是对 HashMap 的线程安全集合进行加锁,或者说直接用 ConcurrentHashMap 集合来实现 CopyOnWriteArraySet 。

    但事实上,源码并不是这么做的。

    从名字来看,和 ConcurrentHashMap 也没有什么关系,而是类似 CopyOnWriteArrayList 的命名,说明是读写单独处理,来让他成为线程安全的集合,那为什么是 ArraySet 多一个 array 修饰语呢?

    494116481e9a7d72b3f1ba0342b4c81a.png

    可以看到,他的思路没有顺延 util 包的 HashSet 的实现思路,而是直接使用了 CopyOnWriteArrayList 作为底层数据结构。也就是说没有利用 Map 的键值对映射的特性来保证 set 的唯一性,而是用一个数组为基底的列表来实现。(那显然在去重方面就要做额外的操作了。)

    然后每一个实现的方法都很简单,基本是直接调用了 CopyOnWriteArrayList 的方法:

    a7eb52d362f2709a0aac52ce49bcc9c8.png

    我们最担心的可能 产生问题的 remove 和 add 方法,也是使用了 CopyOnWriteArrayList 的方法:

    而保证 set 的不重复性质的关键,显然就在于 CopyOnWriteArrayList 的 addIfAbsent 方法,我们还是点进 CopyOnWriteArrayList 源码看一看这个方法的实现:

    8b3259606467b09b2a87fe996e01d758.png

    其中的 indexOfRange 方法:

    d1a89d5700763a25e8935944f1d12213.png

    可以看到,也是加了 Monitor 锁来进行的,整个过程是这样的:

    获取本来的 set ,是一个数组,以快照形式返回当前的数组;

    indexOfRange 方法通过遍历查找查找元素出现位置,addIfAbsent方法完成不存在则加入,如果前一个为 false 后一个就不会执行;

    加锁;

    current 再次获取一次当前的快照,因为有可能第一次判断的过程有了其他线程的插入或者修改操作,此时已经不像等,就进入分支进行判断是否存在;

    否则就要加入这个元素,和 CopyOnWriteArrayList 添加元素的最后操作是一样的;

    解锁。

    总结一下就是,线程安全的 Set 集合完全利用了 CopyOnWriteArrayList 集合的方法,对应的操作也是读写分别处理,写时复制的策略,通过 jvm 层面的锁来保证安全,那么保证不重复的方法就是遍历进行比较。

    这样看来,相比于基于 HashMap 的去重方法,效率肯定会降低,不过如果基于线程安全的 HashMap ,插入操作从hash、比较、到考虑扩容各方面会因为加锁的过程更复杂,而对于一个不重复的 Set 来说,完全没必要,所以应该综合考虑之下采用了 List 为基础,暴力循环去重。

    三、HashMap 的线程不安全

    关于 HashMap 的相关问题,源码里已经分析过,大体是这样的。

    不安全:

    普通读写不一致问题;

    死循环问题;

    ConcurrentModificationException 异常。

    解决:

    util包的Hashtable集合线程安全;

    用 synchronizedMap(new HashMap())包装;

    使用 juc 包的 ConcurrentHashMap。

    HashMap 和 ConcurrentHashMap 的源码分析:

    展开全文
  • ArrayList线程安全处理

    2021-02-01 11:23:43
    //1.Collections.synchronizedList ... 使用线程安全的 CopyOnWriteArrayList 代替线程不安全ArrayList。 List<Object> list2=new CopyOnWriteArrayList<>(); //3.使用ThreadLocal 推荐 Threa
    //1.Collections.synchronizedList
    List<Object> list1= Collections.synchronizedList(new ArrayList<>());
    //2. 使用线程安全的 CopyOnWriteArrayList 代替线程不安全的 ArrayList。
    List<Object> list2=new CopyOnWriteArrayList<>();
    //3.为list.add()方法加锁
    synchronized(list.get()) {
    list.get().add(model);
    }
    //4.使用ThreadLocal  推荐
    ThreadLocal<List<Object>> threadList=new ThreadLocal<List<Object>>(){
        @Override
        protected List<Object> initialValue(){
            return new ArrayList<>();
        }
    };
    
    展开全文
  • Arraylist

    2021-07-29 16:56:21
    ArrayList ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配...
  • ArrayList 非线程安全解决方案,以及触发场景
  • 1、用Vector代替ArrayList,这个回答比较垃圾 2、使用conllections工具类的synchronizedList将new ArrayList放进去可以变安全 3、使用CopyOnWriteArrayList也是比较安全的,CopyOnWriteArrayList比Vector好在用的...
  • 改进的方法很简单,如程序上的注释行一样,使用线程安全的 Vector 代替 ArrayList 即可。 package com.zmkj.admin.test; import java.util.ArrayList; /** * 线程不安全的容器:ArrayList * * ArrayL
  • 文章目录不安全的原因与几种解决方案原因解决方案ArrayList安全的实例线程安全版本的原理 不安全的原因与几种解决方案 多线程场景下如何使用 ArrayList 原因 ArrayList 的 add 操作源码如下: /** * Appends the ...
  • ArrayList详解

    2021-04-27 11:25:08
    ArrayList集合详解 参考https://blog.csdn.net/sihai12345/article/details/79382649 1、简介 ArrayList继承自AbstractCollection类,实现了List接口,底层基于数组实现了容量大小的动态变化,允许null值存在,...
  • ArrayList源码分析

    2021-01-04 08:39:27
    1. ArrayList 简介 ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增...
  • Java面试题(ArrayList

    2021-03-08 01:20:44
    大厂ArrayList的面试题:1.new ArrayList的时候底层new了什么?2.凡数组都有类型,什么类型?3.数组要在内存中占据连续的内存空间,初始值以java8为例是多少?4.存25个元素进去可以吗?底层发生什么?底层扩容到多少...
  • ArrayList源码解析

    2021-03-15 17:56:29
    前言本文是1.8版本的ArrayList源码解析,本文包含了transient,serialVersionUID,深浅拷贝,反射元素创建,扩容逻辑,Fail-Fast机制,数组平移,迭代器等内容的解析。简介ArrayList 是一个数组队列,相当于动态数组...
  • 在日常开发中,ArrayList、Set在大量的场景下使用,然而我们都知道ArrayList和Set是线程不安全的,那么为什么ArrayList和Set是线程不安全的?如何保证在高并发场景下其线程安全性呢?本文将通过以下案例来剖析...
  • 我们已经知道多线程下会有各种不安全的问题,都知道并发...一、List 的不安全1.1 问题看一段代码:public static void main(String[] args) {ArrayList list = new ArrayList<>();for (int i = 0; i < 3; i...
  • 原文链接作者:Stephen C译者:郑旭东校对:方腾飞问:JDK 5在java.util.concurrent里引入了ConcurrentHashMap,在需要支持高并发的场景,我们可以使用它代替HashMap。但是为什么没有ArrayList的并发实现呢?难道在...
  • ArrayList 不是线程安全的,这点很多人都知道,但是线程不安全的原因及表现,怎么在多线程情况下使用ArrayList,可能不是很清楚,这里总结一下 线程不安全的两种体现 查看 ArrayList 的 add 操作源码如下: /** * ...
  • ArrayList-1

    2021-02-07 14:56:08
    1. ArrayList 简介 ArrayList的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加ArrayList实例的容量。这可以减少递增式...
  • ArrayList and Vector

    2013-08-01 10:45:02
    ArrayList和Vector
  • ArrayList核心源码

    2021-04-02 17:13:54
    #ArrayList核心源码 package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; public class ArrayList<E> extends...
  • 1、故障现象ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常2、故障代码public class ArrayListTest {public static void main(String[] args) {List lists = new...
  • ArrayList集合+泛型

    千次阅读 2019-04-19 11:44:50
    泛型是用来规定数据类型的,定义的时候用一个符号代替某种类型 如E 在使用的时候用具体的数据类型 将定义的那个符号替换掉ArrayBox 泛型可以用在: 1.泛型类:在类定义的是hi描述某种数据类型,集合就是这样的 2...
  • 作者:雪山上的蒲公英www.cnblogs.com/zjfjava/p/10217720.htmlArrayList 不是线程安全的,这点很多人都知道,但是线程不安全的原因及表现,怎么在...
  • Java集合源码分析一、ArrayList二、LinkedList 一、ArrayList 继承结构与接口实现 JDK 8.0中ArrayList的变化: ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的...
  • 如果ArrayList调用,不带参数的构造方法,那顺序表的大小0,第一次add时,整个顺序表才变为了10 当这10个放满了,开始扩容,以1.5倍的方式扩容 如果调用的是给定容量的构造方法,顺序表的大小就是你给定的容量,放...
  • **集合讲解(ArrayList、HashMap等)**CollectionListArrayListLinkedListVectorSetMapHashtableHashMapSortedMap数据结构(扩展巩固)线性表队列栈 * 首先我们了解一下集合的用处 最主要的是以前用数组作为容器...
  • ArrayList源码与实现原理

    千次阅读 多人点赞 2021-01-23 15:42:41
    ArrayList简介 在初学Java时就会学习ArrayList、LinkedList这两种JDK中常见的集合类,其中ArrayList本质上是一种动态数组,具有良好的随机访问的性能,即在集合中查找元素的性能比较高,但是其进行元素的插入和删除...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,373
精华内容 13,349
关键字:

安全的arraylist的代替