集合类不安全的问题

ArrayList

当我们构造一个ArrayList的时候,会为我们创建一个初始容量为10的空列表。

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    //构造一个初始容量为 10 的空列表
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

可以看到,elementData是object类型的数组,一开始没有元素时都是空的,当添加第一个元素后会展开为默认容量。

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
     
    transient Object[] elementData; // non-private to simplify nested class access

/**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList线程不安全的案例

ArrayList为了保证并发性,其方法没有用synchronized修饰。
单线程情况没有问题

package listsafe;

import java.util.ArrayList;
import java.util.List;

public class ContainerNotSafeDemo {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        list.forEach(System.out::println);
    }

}

多线程情况

package listsafe;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class ContainerNotSafeDemo {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        for(int i=1; i <= 3 ; i++){
            new Thread(()->{

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

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

}

这些出现了数据丢失。
在这里插入图片描述
这次换成30个线程

package listsafe;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class ContainerNotSafeDemo {

    public static void main(String[] args) {

        List<String> list = 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();
        }
    }

}

出现ConcurrentModificationException(并发修改异常)
在这里插入图片描述
注意代码中的toString,正是由于ArrayList在添加过程中,执行的tostring方法会使用迭代器遍历list,在遍历过程中会有其他线程对其进行修改,导致modCount!=expectedCount引发异常。

解决方案

方法一:用Vector(不过不推荐)并发性下降。

package listsafe;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;

public class ContainerNotSafeDemo {

    public static void main(String[] args) {

        List<String> list = new Vector<>();

        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();
        }
    }

}

在这里插入图片描述
方法二:Collections.synchronizedList(new ArrayList<>())
Collections是java集合工具类,可以用来处理集合问题。

package listsafe;

import java.util.*;

public class ContainerNotSafeDemo {

    public static void main(String[] args) {

        List<String> 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();
        }
    }

}

在这里插入图片描述
方法三:CopyOnWriteArrayList(写时复制)

CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

以下是CopyOnWriteArrayList进行添加操作的源码

大致的流程是:在进行添加操作之前,先利用ReentranLock进行加锁,也就是写的时候只允许一个线程写。然后将原数组进行扩容,扩容的长度是原数组的长度加1,之后将数据加入到新数组的末尾。

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
package listsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class ContainerNotSafeDemo {

    public static void main(String[] args) {

        List<String> list = new CopyOnWriteArrayList<>();

        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();
        }
    }

}

Set

不安全的案例和list一样。

package listsafe;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class SetNotSafeDemo {

    public static void main(String[] args) {

        Set<String> list = new HashSet<>();

        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();
        }
    }
}

在这里插入图片描述

解决方法

方法一:Collections.synchronizedSet

package listsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class SetNotSafeDemo {

    public static void main(String[] args) {

        Set<String> list = Collections.synchronizedSet(new HashSet<>());

        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();
        }
    }
}

在这里插入图片描述
方法二:CopyOnWriteArraySet

可以看到,CopyOnWriteArraySet底层用的还是CopyOnWriteArrayList。

/**
     * Creates an empty set.
     */
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
package listsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

public class SetNotSafeDemo {

    public static void main(String[] args) {

        Set<String> list = new CopyOnWriteArraySet<>();

        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();
        }
    }
}

在这里插入图片描述
在这里多说一下。
HashSet的底层实现用的是HashMap,是将添加的元素作为HashMap的key,其value是一个Object类型的常量。

/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }
 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
private static final Object PRESENT = new Object();

Map

验证

package listsafe;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class MapNotSafeDemo {

    public static void main(String[] args) {

        Map<String,String> map = new HashMap<>();

        for(int i=1; i <= 30 ; i++){
            new Thread(()->{

                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);

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

}

在这里插入图片描述

解决方法:ConcurrentHashMap

package listsafe;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class MapNotSafeDemo {

    public static void main(String[] args) {

        Map<String,String> map = new ConcurrentHashMap<>();

        for(int i=1; i <= 30 ; i++){
            new Thread(()->{

                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);

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

}

在这里插入图片描述

或者是Collections.synchronizedMap

package listsafe;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class MapNotSafeDemo {

    public static void main(String[] args) {

        Map<String,String> map = Collections.synchronizedMap(new HashMap<>());

        for(int i=1; i <= 30 ; i++){
            new Thread(()->{

                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);

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

}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值