ArrayList、HashSet、HashMap是线程不安全

1. ArrayList是线程不安全的,请编写一个不安全的案例并给出解决方案

1.1 ArrayList线程不安全

    private static void NotSafe() {
        List<String> list = new ArrayList<>();//ArrayList没锁,不安全
        for (int i = 0; i <20 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
        //java.util.Concurrent Modification Exception(字面意思:并发修改异常)
    }
部分运行结果:
Exception in thread "14" Exception in thread "17" 
java.util.ConcurrentModificationException

1.2 为什么会发生这种情况

并发正常修改缘由:

一个人正在写入,另一个人又来抢夺,导致数据不一致,并发修改异常。

1.3 三种解决办法

public class ContainerNotSateDemo {
    public static void main(String[] args) {
      //  List<String> list = new ArrayList<>();//ArrayList没锁,不安全
      
      //1.  List<String> list = new Vector<>();
      //使用辅助类
      //2. List<String> list = Collections.synchronizedList(new ArrayList<>());
      //3.写实复制,读写分离(适合读多写少的操作,内存消耗可能也比较大)就是读和写的容器不一样,同时写的话,前一个会加锁,导致后一个等待
        List<String> list = new CopyOnWriteArrayList<>();
        
        for (int i = 0; i <20 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
        //java.util.ConcurrentModificationException并发修改异常
    }
}
  • 对于Vector的add方法添加了加了synchronized,所以保证了在多线程的安全
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
  • 使用辅助类Collections.synchronizedList()通过synchronized创建安全的ArrayLis。

  • CopyOnWriteArrayList.add方法:

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

具体代码:

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

2. HashSet是线程不安全的

问题和解决方法的代码和上方的类似,不过少了一种解决方法,只有两种。

private static void NotSafe1() {
        Set<String> list=new HashSet<>();
       // Set<String> list=Collections.synchronizedSet(new HashSet<>());
       // Set<String> list=new CopyOnWriteArraySet<>();

        for (int i = 0; i <20 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }

了解一下HashSet

  • HashSet的底层是HashMap,创建了一个底层容量大小为16,负载原子是0.75的对象(其负载原子的作用在于当容量大于16*0.75的时候,HashSet会自动进行扩容,16为一个不断可以不断更新的size())
public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
  • HashSet的add()方法,只有一个变量,为底层HashMap中的key,而HashMap中的value则是HashSet类中中的一个present的常量对象,是一个恒定的。
    public HashSet() {
        map = new HashMap<>();
    }
    
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

3. HashMap是线程不安全的

对比上方的内容,HashMap也有两种类似的可以实现线程安全的方法。

private static void NotSafe2() {
        //Map<String, String> map = new HashMap<>();
        // Map<String,String> map=new ConcurrentHashMap<>();
         Map<String,String> map=Collections.synchronizedMap(new HashMap<>());

        for (int i = 0; i <20 ; i++) {
            new Thread(()->{
            
                map.put(Thread.currentThread().getName(),
                UUID.randomUUID().toString().substring(0,8));
                
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
  • 为什么HashMap是线程不安全的呢?
  1. 在多线程的情况下,两个线程同时去执行同一样操作,加入数put操作,有可能同时存储,导致数据的覆盖问题。
  2. 扩容的时候,可能会出现死循环的情况。原因:重新定位每一桶的下标,并采用头插法将元素迁移到新数组中,而头插法会将链表的顺序翻转,a、b两个线程同时进行扩容操作,会形成换链表。

来个面试题—Arraylist 与 LinkedList 区别?

        来个面试题爽一下。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值