zoukankan      html  css  js  c++  java
  • java.util(ArrayList)

    
    
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final int DEFAULT_CAPACITY = 10; // ArrayList中Object[]中的默认初始容量
        private static final Object[] EMPTY_ELEMENTDATA = {};  //空Object[]数组对象
        private transient Object[] elementData;  //定义了一个私有的未被序列化的数组elementData,用来存储ArrayList的对象列表
        private int size;  //ArrayList中实际数据的数量
        
        public ArrayList(int initialCapacity) {  //带容量的构造函数
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            this.elementData = new Object[initialCapacity];
        }
        
        public ArrayList() { //无参构造函数,默认容量为10,这里的1暂时还没有设置,在add(E)的时候会指定
            super();
            this.elementData = EMPTY_ELEMENTDATA;
        }
        
        public boolean add(E e) {
        //  确保数组容量足够添加元素进入数组
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
        
        private void ensureCapacityInternal(int minCapacity) {
            if (elementData == EMPTY_ELEMENTDATA) {  
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1); //10->16->25->38->58->88->...
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);//意思就是把原数组内容复制到行数组上,容量变大了,将引用赋给elementData
        }
        
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }

    以上是jdk1.7的描述,结论如下:

      ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长;

       ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。

      ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

      注意扩充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组(详见下面的第3点)。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList。

      ArrayList的数组扩容调用了Arrays.copyof(),该方法实际上是在其内部又创建了一个长度为newlength的数组,调用System.arraycopy()方法,将原来数组中的元素复制到了新的数组中。System.arraycopy()方法。该方法被标记了native,调用了系统的C/C++代码,在JDK中是看不到的,但在openJDK中可以看到其源码。该函数实际上最终调用了C语言的memmove()函数,因此它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。

    多线程安全问题分析

    private transient Object[] elementData;  //定义了一个私有的未被序列化的数组elementData,用来存储ArrayList的对象列表

    ArrayList内部是使用数组保存元素的,在ArrayList中此数组即是共享资源,当多线程对此数据进行操作的时候如果不进行同步控制,即有可能会出现线程安全问题。

    一: add方法可能出现的问题分析

    首先我们看一下add的源码如下:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 是否需要扩容
        elementData[size++] = e;//赋值
        return true;
    }

    1:下标越界问题

      多个线程进入ensureCapacityInternal()并执行完毕,此时都不需要扩容,依次赋值时会size+1,所以从第二个开始的线程赋值时其下标很可能超过了容量值,赋值时就报错了

    2:存入的值变为null  

      elementData[size++] = e是先赋值再size+1,多线程运行到赋值还没+1时,size位置上被覆盖了多次,然后多次+1,size+1,+2等位置没赋值过,下次就直接从size+n开始赋值,看起来就add了null值一样,此时不会报错,因为add时没有null所以取出时没做考虑就可能报NullPointerException了.

    3.数据个数小于预期值

      在多线程操作下 size++不是原子操作,会出现最终数据元素个数小于期望值。

    代码验证

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Test4 {
        private static List<Integer> list = new ArrayList<Integer>();
        private static ExecutorService executorService = Executors.newFixedThreadPool(1000);
        
        private static class IncreaseTask extends Thread{
            @Override
            public void run() {
                System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!");
                for(int i =0; i < 100; i++){
                    list.add(i);
                }
                System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!");
            }
        }
        
        public static void main(String[] args) {
            for(int i=0; i < 1000; i++){
                executorService.submit(new IncreaseTask());
            }
            executorService.shutdown();
            while (!executorService.isTerminated()){
                try {
                    Thread.sleep(1000*10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("All task finished!");
            System.out.println("list size is :" + list.size());
        }
    }

     从以上执行结果来看,最后输出的结果会小于我们的期望值。即当多线程调用add方法的时候会出现元素覆盖的问题。

  • 相关阅读:
    10-SAP PI开发手册-ERP发布服务供外围系统调用(RFC类型)
    出口增强:已知SMOD查CMOD
    SAP开发系统中开发和配置客户端请求号变更
    ABAP接口之Http发送json报文
    ABAP 常见系统表
    .net程序员转战android第三篇---登录模块之静态登录
    .net程序员转战android第二篇---牛刀小试
    .net程序员转战android第一篇---环境部署
    将一个字符串转变成2元素的数组
    字节小程序客服推送消息
  • 原文地址:https://www.cnblogs.com/myseries/p/7538951.html
Copyright © 2011-2022 走看看