7. JUC集合类不安全

*简介

集合类在迭代的时候,如果同时对其修改就会抛出java.util.ConcurrentModificationException并发修改异常

List

例子

public class NotSafeDemo {
    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();
        }
    }
}

*原因

因为ArrayList的add方法中没有加锁的操作,有30个线程要对ArrayList操作,然后线程又要写,又要读,所以会出现上述的报错。

解决方案

方案一:Vector

查看源码,发现方法上有synchronized,所以线程安全

虽然保证了数据一致性,但是查询效率下降了,不推荐

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

方案二:Collections

Collections提供了方法synchronizedList可以将一个线程不安全的ArrayList转化为一个线程安全的

小数据量的时候完全可以使用这种方式

		List<String> list = Collections.synchronizedList(new ArrayList<>());

那HashMap,HashSet是线程安全的吗?也不是
所以有同样的线程安全方法

方案三:*写时复制

image-20201211232629655

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

CopyOnWrite容器即写时复制的容器。

往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。添加元素完后,再将原容器的引用指向新的容器setArray(newElements)。

这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。

所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
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();
    }
}

Set

例子

    public static void setNotSafe() {
        Set<String> set = new HashSet<>();
        for (int i = 1; i <=30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }

解决方案

方案一:Collections

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

方案二:写时复制

		//底层还是new CopyOnWriteArrayList<>()
        Set<String> set = new CopyOnWriteArraySet<>();

*HashSet理解

HashSet的底层是HashMap

HashSet的add()调的是HashMap的put()

HashMap中put()的那个key就是HashSet的值,value是一个叫做PRESENT的Object常量

Map

例子

    public static void mapNotSafe() {
        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();
        }
    }

解决方案

方案一:Collections

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

*方案二:ConcurrentHashMap

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

*HashMap理解

HashMap底层是一个node类型的节点,由node类型的数组、node类型的链表、红黑树组成。

HashMap的数组长度是16,负载因子0.75

平时调用的new HashMap<>()其实就是调用的new HashMap<>(16, 0.75)

image-20201212111747612

如果有特殊长度的需求,可以直接调用包含initialCapacity的重载,省去了从默认长度扩容的时间,提升了效率。

HashMap默认扩容为原来的一倍,16,32,64…

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值