• JUC系列(三) 不安全的集合类


    📣 📣 📣 📢📢📢
    ☀️☀️你好啊!小伙伴,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。
    📒 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具
    📒 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章
    📒 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习
    ✏️高质量技术专栏专栏链接: 微服务数据结构netty单点登录SSMSpringCloudAlibaba
    😝公众号😝想全栈的小冷,分享一些技术上的文章,以及解决问题的经验
    当前专栏JUC系列

    集合类不安全

    List不安全

    并发 arrayList是不安全的
    解决方案:

    1. vector 线程安全,因为新增方法前 带了 sync关键字,这个方法并不是最优解
    2. 集合工具类 Collections.synchronizedList(new ArrayList<>());
    3. JUC包下 CopyOnWriteArrayList<>()
    
    • 1
    • 2
    • 3

    CopyOnWrite 写入时复制,COW 计算机程序设计领域的一种优化策略

    如 : 比如多个调用者调用同一个list,读取的时候,固定的,写入(覆盖)

    在写入的时候 避免覆盖,造成数据问题; 读写分离

    问题: 为什么不用vector 为什么呢?

    答 : 因为只要使用 sync关键字,效率都会低一些,而CopyOnWriteArrayList,底层的方法是用的lock锁

    //java.util.ConcurrentModificationException 并发修改异常
    public class ListTest {
        public static void main(String[] args) {
            //并发 arrayList是不安全的
            /*  解决方案
             * 1. vector 线程安全,因为新增方法前 带了 sync关键字,这个方法并不是最优解
             * 2. 集合工具类 Collections.synchronizedList(new ArrayList<>());
             * 3. JUC包下 CopyOnWriteArrayList<>()
             *  CopyOnWrite 写入时复制,COW 计算机程序设计领域的一种优化策略
             * 如 : 比如多个调用者调用同一个list,读取的时候,固定的,写入(覆盖)
             * 在写入的时候 避免覆盖,造成数据问题;
             * 读写分离
             *
             * 问题: 为什么不用vector 为什么呢? 因为只要使用 sync关键字,效率都会低一些,而CopyOnWriteArrayList,底层的方法是用的lock锁
             * */
            List<String> list = new CopyOnWriteArrayList<>();
            for (int i = 1; i <= 600; i++) {
                new Thread(() -> {
                    list.add(UUID.randomUUID().toString().substring(0, 5));
                    System.out.println(list);
                }, String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    写入时复制的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();//释放锁
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Set不安全

    解决方案 :

    • 首先就是 Collections工具类 Collections.synchronizedSet(new HashSet());
    • 就是和集合再同一个解决方案里的 Set set = new CopyOnWriteArraySet();
    public class SetTest {
        //java.util.ConcurrentModificationException 同理和集合没有什么区别
        public static void main(String[] args) {
            /* 如何解决hashset 线程不安全?
             * 1. 首先就是 Collections工具类 Collections.synchronizedSet(new HashSet());
             * 2. 就是和集合再同一个解决方案里的  Set set = new CopyOnWriteArraySet();
             * */
            Set set = new CopyOnWriteArraySet();
            for (int i = 1; i <= 600; i++) {
                new Thread(() -> {
                    set.add(UUID.randomUUID().toString().substring(0, 5));
                    System.out.println(set);
                }, String.valueOf(i)).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    HashSet的底层 是 hashmap

    public HashSet(){
        map = new HashMap<>();
    }
    
    //add set 的本质就是map key 是无法重复的
    public boolean add(E e){
        return map.put(e,PRESENT)==null;
    }
    
    private static final Object PRESENT = new Object //不变的值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Map类不安全

    HashMap原理简单回顾

    image-20220302112806560

    解决方案:

    • Map map = new ConcurrentHashMap<>();
    public class mapTest {
        //java.util.ConcurrentModificationException
        public static void main(String[] args) {
            //    map是这样用的吗 不是 工作中很少使用 Hashmap
            //    默认等价于什么 new HashMap<>(16,0.75);
            /*解决方案
             *
             * */
            Map<String, String> map = new ConcurrentHashMap<>();
            for (int i = 1; i <= 500; i++) {
                new Thread(() -> {
                    map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                    System.out.println(map);
                }, String.valueOf(i)).start();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ConcurrentHashMap原理

    		ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
    
    • 1

    ConcurrentHashMap的内部结构

    image-20220309163044870

    从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

  • 相关阅读:
    微服务环境搭建
    C语言航路外传之隐式转换与优先级的那点事(你程序总是出bug的一个重要原因)
    04-垃圾收集器ParNew&CMS三色标记
    通过低代码平台搭建客服管理系统,解决管理难题
    第142篇:原生js实现响应式原理
    使用SpaceDesk实现iPad成为电脑拓展屏(保姆级教程)
    学习总结week6_1excel入门
    elenium定位element-plus框架渲染之后的页面
    裸金属服务器是什么
    视觉与机器人的九点标定(一)
  • 原文地址:https://blog.csdn.net/doomwatcher/article/details/127603222