List在高并发场景下保证线程安全的三种方案,以及CopyOnWriteArrayList写时复制读写分离

470 阅读2分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

Java并发修改异常:java.util.ConcurrentModificationExpection

vector线程安全但是是JDK1.0诞生
arrayList线程不安全但是是JDK 1.2诞生

Collection是一个接口
Collections是一个辅助工具类

目前两种解决方案:new vector<>();
collctions.synchronizedList(new ArrayList<>());
还有改错思路思想
在这里插入图片描述

//list线程不安全问题,已经举一个不安全的例子
import java.util.*;

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

        //1.方案一
        //List<String> list = new Vector<>();

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


        //List<Integer> list = Arrays.asList(1, 2, 3);

        //list.forEach(System.out::println);

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

在这里插入图片描述

第三种方法 使用new CopyOnWriteArrayList<>();可以保证线程安全

创建:CopyOnWriteArrayList() 添加元素:即add(E)方法 获取单个对象:即get(int)方法 删除对象:即remove(E)方法 遍历所有对象:即iterator(),在实际中更常用的是增强型的for循环去做遍历 注:CopyOnWriteArrayList是一个线程安全,读操作时无锁的ArrayList。

List list = new CopyOnWriteArrayList();

  1. CopyOnWriteArrayList(写数组的拷贝)是ArrayList的一个线程安全的变体,CopyOnWriteArrayList和CopyOnWriteSet都是线程安全的集合,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。

  2. 它绝对不会抛出ConcurrentModificationException的异常。因为该列表(CopyOnWriteArrayList)在遍历时将不会被做任何的修改。

  3. CopyOnWriteArrayList适合用在“读多,写少”的“并发”应用中,换句话说,它适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作是加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。

  4. CopyOnWriteArrayList的功能是是创建一个列表,有三种构造方法:

(1)CopyOnWriteArrayList ()创建一个空列表。

(2)CopyOnWriteArrayList (Collection<? extendsE> c)

创建一个按 collection的迭代器返回元素的顺序包含指定 collection元素的列表。

(3)CopyOnWriteArrayList(E[] toCopyIn)

创建一个保存给定数组的副本的列表

复制后前面指针作废,并且再最后加上元素
在这里插入图片描述

这是add的源码:
在这里插入图片描述
笔记,排错后的操作
在这里插入图片描述
什么是写时复制
在这里插入图片描述
第三种方式小demo

//list线程不安全问题,已经举一个不安全的例子
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

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

        //1.方案一
        //List<String> list = new Vector<>();

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

        //3.方案三
        List<String> list = new CopyOnWriteArrayList<>();

        //List<Integer> list = Arrays.asList(1, 2, 3);

        //list.forEach(System.out::println);

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

Set多线程并发不安全问题
hashset底层是hashmap
在这里插入图片描述

value恒定present常量
在这里插入图片描述

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

public class setNotSafe {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        //方案一
        //Set<Object> set = Collections.synchronizedSet(new HashSet<>());
        //方案二
        //Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i <= 30; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

Map集合多线程并发不安全问题,依然还是那两种方法



import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

public class mapNotSafe {
    public static void main(String[] args) {
        //HashMap<String,String> map = new HashMap<>();
        //方案一
        Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
        //方案二
        //Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i <= 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}