集合类线程不安全

集合类是线程不安全


证明集合类是线程不安全


前言

在多线程编程中,日常用的集合类存在并发情况下,可变操作(add,set,等)无法保证数据一致的情况


提示:以下是本篇文章正文内容,下面案例可供参考

一、证明集合不安全

1.代码演示:

代码演示如下(示例):

package com.study.thread;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
 * 模拟多线程添加数据,并打印
 */
public class NoSafeDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

此时出现的结果(示例):

Exception in thread "0" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at com.study.thread.NoSafeDemo.lambda$main$0(NoSafeDemo.java:13)
	at java.lang.Thread.run(Thread.java:748)

在这里插入图片描述
说明:这个异常类为ConcurrentModificationException,直译过来就是并发修改异常,源码说明为,只有在修改被拒绝时才会被检测到,由此可得知,并发情况下,在ArrayList中调用add()方法的同时对其进行修改会抛出java.util.ConcurrentModificationException异常。
ArrayList中的add(E e)方法源码:

public boolean add(E e) {
     ensureCapacityInternal(size + 1);  // Increments modCount!!
     elementData[size++] = e;
     return true;
}

没有synchronized关键字导致线程不安全

2.解决方案1,使用Vector集合(不推荐使用,官网已放弃):

代码如下(示例):

package com.study.thread;
import java.util.List;
import java.util.UUID;
import java.util.Vector;

/**
 * 模拟多线程添加数据,并打印
 */
public class NoSafeDemo {
    public static void main(String[] args) {
        List<String> list = new Vector<>(); //将ArrayList改成Vector
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

使用Vector集合类,是不会抛ConcurrentModificationException
Vector的add(E e)源码如下:

public synchronized boolean add(E e) {
	    modCount++;
	    ensureCapacityHelper(elementCount + 1);
	    elementData[elementCount++] = e;
	    return true;
}

有synchronized线程安全

一般不使用Vector,Vector类中,大多数方法都是直接使用synchronize关键字修饰,性能上会差很多,线程安全的实现方法比较粗暴且效率低。(synchronize关键字修饰的方法,是能够保证线程安全,但是会阻塞其他线程的访问其他synchronize修饰的方法,而vector恰好大多数方法都使用synchronize关键字修饰,其直接结果就是导致效率插入,删除时,效率低)

引申:java.util,Stack因为extends Vector,所以也不推荐使用,可以使用java.util.Deque双端队列来实现队列与栈的需求。

2.解决方案2,使用Collections集合工具类:

代码如下(示例):

package com.study.thread;
import java.util.List;
import java.util.UUID;
import java.util.Collections;

/**
 * 模拟多线程添加数据,并打印
 */
public class NoSafeDemo {
    public static void main(String[] args) {
    	//使用集合工具类Collections中的synchronizedList(List<T> list)
        List<String> list = Collections.synchronizedList(new ArrayList<>()); 
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

使用集合工具类Collections中的synchronizedList(List list),
是不会抛ConcurrentModificationException
Collections的synchronizedList(List list)源码如下:
在这里插入图片描述
返回一个指定的集合线程安全list,来保证同步线程安全

同样对于HashMap<K,V>,HashSet<K,V>在Collections工具类中也有对应的线程安全方法
HashMap<k,v>保证线程安全如下:在这里插入图片描述

HashSet<k,v>线程安全如下:
在这里插入图片描述

Collections作为java.uril中的工具类,包含了常用的集合的线程安全操作

2.解决方案3,使用CopyOnWriteArrayList集合:

代码如下(示例):

package com.study.thread;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 模拟多线程添加数据,并打印
 */
public class NoSafeDemo {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>(); 
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

使用java.util.concurrent中的CopyOnWriteArrayList,是不会抛ConcurrentModificationException
CopyOnWriteArrayList源码说明,CopyOnWriteArrayList是ArrayList的一种线程安全的变体,其中所有的可变操作(add,set,等)都是通过实现底层array(数组)生产新副本来实现的
在这里插入图片描述

CopyOnWriteArrayList的add(E e)源码如下:
在这里插入图片描述

通过对add方法加锁的方式来实现线程安全操作

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

3.拓展类比

HashSet<>()线程不安全
CopyOnWriteArraySet 线程安全
在这里插入图片描述

HashMap<>() 线程不安全
ConcurrentHashMap<K,V> 线程安全
在这里插入图片描述
待说明


总结

本节总结了线程并发时,集合的可变操作(add,set等)安全性分析,使用了3种不同的方式对List集合进行thread-safe说明,vector,collections,copyOnWriteArrayList,其中copyOnWriteArrayList为java.util.concurrent并发包下的一个线程安全类,能够保证操作list时候的线程安全,同时解释了在并发情况下,add操作线程安全的原理。


版权声明:本文为weixin_38289612原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_38289612/article/details/119578762