List、Map并发解决方法

List、Map并发解决方法

1、出现现象

当我们用多个线程对ArrayList进行添加操作的时候,会出现异常信息 ConcurrentModificationException
下面放上代码

public class ListTest {
    public static void main(String[] args) {
        //ArrayList 不安全的  synchronized
        List<Object> list = new ArrayList<>();

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

        }
    }
}

在这里插入图片描述

2、解决办法

由上方的案例可以发现,当多线程的环境下ArrayList会出现异常,list、map在日常开发学习中都是使用的比较多的,这个问题如何解决呢?
第一种 比较简单的方法,我们可以用 synchronized 修饰,此时无报错信息

List<Object> list = new ArrayList<>();
        Object obj = new Object();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                synchronized (obj){
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                }
            },String.valueOf(i)).start();
        }

此外还可以用List list = new Vector<>();来解决,在他的每个add方法里面加了修饰,无异常报错

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

第三种方法 使用Collections.synchronizedList方法解决,此时代码正常运行,

List<Object> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 100; i++) {
            new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
            },String.valueOf(i)).start();
        }

这又是为何呢?点进去查看源码,原来在SynchronizedList里面对每个方法用synchronized +mutex对象锁的方式进行了保护,阻止了并发修改

//点击synchronizedList,查看源码
List<Object> list = Collections.synchronizedList(new ArrayList<>());

===========================
//Collections里面的synchronizedList方法
public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list)); //最终都是要走到这个里面
    }
//这里我们传的参数是ArrayList,他是RandomAccess的实现类(ArrayList实现了它),
//走的第一个SynchronizedRandomAccessList

===========================
//再来看Collections的内部类SynchronizedRandomAccessList,它继承了SynchronizedList
 static class SynchronizedRandomAccessList<E>
        extends SynchronizedList<E> implements RandomAccess {

        SynchronizedRandomAccessList(List<E> list) {
            super(list);//点进去,走到了SynchronizedList
        }
}

===========================
//Collections的内部类SynchronizedList,它继承了SynchronizedCollection
 static class SynchronizedList<E>
        extends SynchronizedCollection<E> implements List<E> {
        private static final long serialVersionUID = -7754090372962971524L;

        final List<E> list;

        SynchronizedList(List<E> list) {
            super(list);//点进去
            this.list = list;
        }

		//可以看到这里的 get/set方法中都看到了synchronized代码快,其中使用的是mutex进行修饰
		public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
        public E remove(int index) {
            synchronized (mutex) {return list.remove(index);}
        }
}

===========================
//Collections内部类SynchronizedCollection 
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }
}

以上三种方法本质上都是加了Synchronized来解决他们的并发问题,下面提供一种新的思路吧。
.

第四种:使用CopyOnWriteArrayList的方式对list进行添加,此时也是正常运行

List<String> list = new CopyOnWriteArrayList<>();
        Object obj = new Object();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
            },String.valueOf(i)).start();
        }

CopyOnWriteArrayList 英语并不好的我也能通过名字看出,在写的时候复制,简称写时复制
在这里插入图片描述在来看一下CopyOnWriteArrayList 他的源代码

List<String> list = new CopyOnWriteArrayList<>();//点进去


lass CopyOnWriteArrayList<E>{
	//volatile 修饰,保证可见性和有序性,不保证原子性
	private transient volatile Object[] array;
	
	//构造函数创建了一个长度为0的Object数组
	public CopyOnWriteArrayList() {
	        setArray(new Object[0]);
	    }

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

从上面的代码可以看到,我们在做add操作的时候,先创建了一个新的数组,然后将原有数组数据复制到新数组种,最好在新数组末尾添加value,这些过程通过加锁修饰了,解决了他们的并发问题。

CopyOnWriteArrayList为什么并发安全且性能比Vector好
Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得
锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性
能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。

不过这也要根据具体实际情况来考虑,上面代码也能看到,每次添加的时候都会创建一个数组对象,新的数组对象长度只是老数组长度+1,这在写操作多的时候 开销也是很大的,需要不断地区创建对象,复制数据。中间过程中也浪费了许多的内存空间,加大了垃圾回收的程度。

public class ListTest {
    public static void main(String[] args) {
        //ArrayList 不安全的  synchronized
        /*解决方法
        List<Object> list = new Vector<>();
        List<Object> list = Collections.synchronizedList(new ArrayList<>());
        List<String> list = new CopyOnWriteArrayList<>();
        * */
        //CopyOnWrite 写入时复制 cow 优化策略
        //多个线程调用时,list 读取时时固定的,写入(覆盖)
        //在写入时避免覆盖 造成数据问题
        List<String> list = new CopyOnWriteArrayList<>();
//        List<Object> list = new ArrayList<>();
        Object obj = new Object();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

Set解决办法总体相似
也是上面几个方法,过程就不叙述了,上码吧

/**
 * java.util.ConcurrentModificationException
 */
public class SetTest {
    public static void main(String[] args) {
       // Set<String> set = new HashSet<>();
         /*解决方法
        Set<String> set = new HashSet<>();
        Set<String> set = Collections.synchronizedList(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        * */
        Set<String> set = new CopyOnWriteArraySet<>();

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

        }
    }
}

3、Map

Map的话 可以使用Hashtable 、 Collections.synchronizedMap、ConcurrentHashMap三种方式

public class MapTest {
    public static void main(String[] args) {
        //new HashMap(16,0.75)
//        Map<String, String> map = new HashMap<>();
//        Map<Object, Object> map = new Hashtable<>();
//        Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 40; i++) {
            new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

前面两种比较简单,第三种看链接https://blog.csdn.net/weixin_44460333/article/details/86770169

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值