4_ Collection 线程不安全的举例

单线程环境下

单线程环境的 ArrayList 是不会有问题的

public class ArrayListNotSafeDemo {
    public static void main(String[] args) {

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

        for(String element : list) {
            System.out.println(element);
        }
    }
}

多线程环境

为什么 ArrayList 是线程不安全的 ?因为在进行写操作的时候,方法上为了保证并发性,是没有添加 synchronized 修饰,所以并发写的时候,就会出现问题 。

在这里插入图片描述


ArrayList 不安全的案列

当我们同时启动 100个线程去操作 List 的时候

public class Demo5 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        for(int i = 0;i < 100;i++){
            new Thread(() ->{
                //往集合中添加元素【add 方法并未 synchronized 修饰】
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

这个时候出现了错误,也就是java.util.ConcurrentModificationException[ 并发修改的异常 ]

在这里插入图片描述

导致原因

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

解决方案
方案一:Vector

第一种方法,就是不用 ArrayList 这种不安全的 List 实现类,而采用 Vector,线程安全的

关于 Vector 如何实现线程安全的,而是在方法上加了锁,即synchronized

在这里插入图片描述

这样就每次只能够一个线程进行操作,所以不会出现线程不安全的问题,但是因为加锁了,导致并发性下降 。

方案二:Collections.synchronizedList()
List<String> list = Collections.synchronizedList(new ArrayList<>());

采用 Collections 集合工具类,在 ArrayList 外面包装一层 同步 机制 。

方案三:使用 JUC 工具类中的 CopyOnWriteArrayList 类

CopyOnWriteArrayList:写时复制,主要是一种读写分离的思想

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

就是写的时候,把 ArrayList 扩容一个出来,然后把值填写上去,在通知其他的线程,ArrayList 的引用指向扩容后的

查看底层 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();
        }
    }

首先需要加锁

final ReentrantLock lock = this.lock;
lock.lock();

然后在末尾扩容一个单位

Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);

然后在把扩容后的空间,填写上需要 add 的内容

newElements[len] = e;

最后把内容 set 到 Array 中


HashSet 线程不安全

异常类型

线程不安全。并发情况下也会发生 ConcurrentModificationException 异常;

解决方案
方案一:Collections.synchronizedSet(new HashSet<>())
方案二:使用 JUC 工具类中的 CopyOnWriteArraySet 类

底层还是使用 CopyOnWriteArrayList 进行实例化
在这里插入图片描述

补充:

同理 HashSet 的底层结构就是 HashMap

在这里插入图片描述

思考: 但是为什么我调用 HashSet.add() 的方法,只需要传递一个元素,而 HashMap 是需要传递 key-value 键值对 ?

首先我们查看 hashSet 的 add 方法

  public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

我们能发现但我们调用 add 的时候,存储一个值进入map中,只是作为key进行存储,而 value 存储的是一个Object 类型的常量,也就是说 HashSet 只关心key,而不关心 value 。


HashMap 线程不安全案列

同理 HashMap 在多线程环境下,也是不安全的,并发情况下也会发生 ConcurrentModificationException 异常;

public static void main(String[] args) {

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

        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
解决方案
方案一:使用 HashTable

与 Vector 类似,属于 HashMap 线程安全的实现类,里面方面同样加了 synchronized修饰,效率较低 。

方案二:Collections.synchronizedMap(new HashMap<> ())
方案三:使用 JUC 工具类下的 ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值