List容器线程安全,Queue

区别:

ArrayList底层是数组结构,查询、修改快O(1),增加删除慢O(n),线程不安全,效率较高。

LinkedList底层是双向链表数据结构,查询、修改慢O(n),增删快O(1),线程不安全,效率高。

Vector 底层是数组结构,可以看作是 ArrayList 的线程安全版本,效率较低。

注意:

所谓的线程安全,不包括对这些集合类的复合操作;当第一个线程对复合操作时,第二个线程也进入未同步的复合操作,就会出现问题。但多线程单个去操作线程安全集合中的方法时,是可靠安全的

该问题可参考:https://blog.csdn.net/z960339491/article/details/73293894

ArrayList:

注意点:

初始大小:10

扩容计算:int newCapacity = oldCapacity + (oldCapacity >> 1);向右移位1相当于除二,即扩容为原来的1.5倍

建议设置 ArrayList 初始容量,因为这样能够节省掉很多次内存分配空间和数据搬移的操作。

不能存储八大基本数据类型,需要使用其对应的包装类。

为什么ArrayList是线程不安全的: 

可以认为ArrayList是自增扩容的数组,在对其进行add操作时,源码中会调用到private void grow(int minCapacity)方法,这个方法是非同步的,所以多线程操作时,一旦线程在这个方法中被挂起,其他线程继续操作就会出现问题

三种不同的List创建方法

 public static void main(String[] args) {
//        List<String> data = new ArrayList<>();                                //这种方法创建的data,在多个线程对他同时的操作中,会出现问题
//        List<String> data=new Vector<>();                                    //线程安全
        List<String> data = Collections.synchronizedList(new ArrayList<>());  //这种方法创建的data,是线程安全的
//        CopyOnWriteArrayList<String> data = new CopyOnWriteArrayList();         //该方法创建的data,操作速度为什么会比上面的慢很多??


        // 用来让主线程等待100个子线程执行完毕
        CountDownLatch countDownLatch = new CountDownLatch(10000);
        // 启动100个子线程
        for (int i = 0; i < 10000; i++) {
            SampleTask task = new SampleTask(data, countDownLatch);
            Thread thread = new Thread(task);
            thread.start();
        }

        try {
            // 主线程等待所有子线程执行完成,再向下执行
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // List的size,正常情况下,10000*100
        System.out.println(data.size());
    }
}

class SampleTask implements Runnable {
    CountDownLatch countDownLatch;
    List<String> data;

    public SampleTask(List<String> data, CountDownLatch countDownLatch) {
        this.data = data;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        // 每个线程向List中添加100个元素
        for (int i = 0; i < 100; i++) {
            data.add("添加第 " + i + "个元素!");
        }
        // 完成一个子线程
        countDownLatch.countDown();
    }

remove(int index):

将指定下标后面一位到数组末尾的全部元素向前移动一个单位,并且把数组最后一个元素设置为null,这样方便之后将整个数组不再使用时,会被GC

 /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        // 需要移动的元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

为什么不能在 ArrayList 的 For-Each 循环中删除元素

现象:

        List<Object> integers = new ArrayList<>();
        integers.add("0");
        integers.add("1");

        for (Object o : integers) {
            if ("1".equals(o)) {
                integers.remove(o);
            }
        }

        System.out.println(integers);

在使用 for-each 方式删除 ArrayList 中非倒数第二个元素时,会抛出异常(ConcurrentModificationException)。但在使用其他方式删除 ArrayList 元素时,却没有问题。

原因:

for-each 方式在遍历 ArrayList 中的元素时,会使用内部类 Itr (Itr 实现了 Iterator 接口)的 hasNext() 和 next() 方法。

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

    1. 在调用 ArrayList.remove( object ) 时,最终会执行下面的 fastRemove 方法。ArrayList.modCount 会自增1,并且会对 ArrayList 的 size 自减操作;但 ArrayList 内部类 Itr.expectedModCount 值不会改变;

private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

    2. foreach循环中,被调用到 Itr 的 hasnext() 方法时,如果上一步移除的是倒数第二个元素,  那么 cursor == size(cursor 的值来自于上一次调用 Itr.next() 方法确定,为原size()-2+1 = 原size()-1;此时 size 也为原size()-1),hasnext 将返回false,foreach循环停止;其他情况的hasnext将true,foreach循环继续执行;

    3. 此时会调用 Itr 的 next 方法,然后执行 checkForComodification 方法,该方法检查 ArrayList 的 modCount 和 内部类 Itr.expectedModCount 的值是否相等,由于第一步中的操作,两者不相等,所以抛出异常。

为什么Vector是线程安全的:

该类中的方法都是加入了synchronized关键字的。

ArrayList、LinkedList、Vector源码解析参考:https://www.cnblogs.com/lxd-ld/p/9927214.html

CopyOnWriteArrayList

特点:

1. 使用 ReentrantLock 保证线程安全

2. 增、删、改操作都会复制一份新数组,在新数组中做修改,修改完了再用新数组替换老数组

3. 采用读写分离的思想,读操作不加锁,写操作加锁,且写操作空间复杂度为O(2n)(n为数组长度),所以适用于读多写少的场合

4.由于读操作不加锁,所以 CopyOnWriteArrayList 只保证最终一致性,不保证实时一致性

Queue:LinkedList适合FIFO的队列

/**
 * 队列测试:实现类使用LinkedList,适合FIFO
 *
 * Queue也有很多其他的实现类,比如java.util.concurrent.LinkedBlockingQueue。
 * LinkedBlockingQueue是一个阻塞的线程安全的队列,底层实现也是使用链式结构。
 */
public class TestQueue {

    // 定义一个队列
    Queue<String> queue;
    @Before
    public void before() {
        // 实例化队列变量
        queue = new LinkedList<String>();
        // add方法向队列中添加元素,返回布尔值,add方法添加失败时会抛异常,不推荐使用
        // queue.add("1");

        // offer方法向队列中添加元素,返回布尔值
        queue.offer("a");
        queue.offer("b");
        queue.offer("c");
        queue.offer("d");
    }
    @Test
    public void test1() {
        // poll方法移除队列首个元素并返回,若队列为空,返回null
        String pollEle = queue.poll(); //  弹出元素,先进先出,弹出了a
        System.out.println(pollEle); // a
        System.out.println(queue); // [b, c, d, e]

        // peek方法返回队列首个元素,但不移除,若队列为空,返回null
        String peek = queue.peek(); // 查看首个元素
        System.out.println(peek);
        System.out.println(queue); // [b, c, d, e]
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值