Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 415|回复: 0

Java 集合类的线程安全问题及解决方法

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-6-28 06:54:30 | 显示全部楼层 |阅读模式

    一、List

    1.1 模拟多线程环境

           多线程环境下,会抛出 java.util.ConcurrentModificationException 异常

      1 public static void listNotSafe() {
      2     List<String> list = new CopyOnWriteArrayList<>();
      3 
      4     for (int i = 0; i < 30; i++) {
      5         new Thread(() -> {
      6             list.add(UUID.randomUUID().toString().substring(0, 8));
      7             System.out.println(list);
      8         }).start();
      9     }
     10 }
    image

    1.2 异常原因

       多线程环境下,并发争抢修改导致出现该异常。

    1.3 解决办法

      1 // 1. 使用线程安全类 Vector
      2 new Vector();
      3 
      4 // 2. 使用 Collections 工具类封装 ArrayList
      5 Collections.synchronizedList(new ArrayList<>());
      6 
      7 // 3. 使用 java.util.concurrent.CopyOnWriteArrayList;
      8 new CopyOnWriteArrayList<>();

    1.4 写时复制思想

       CopyOnWrite 容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy, 复制出一个新的容器Object[] newElements, 然后新的容器Object[] newElements 里添加元素,添加完元素之后,再将原容器的引用指向新的容器 setArray(newElements); 这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
      1 // CopyOnWriteArrayList.java
      2 public boolean add(E e) {
      3     final ReentrantLock lock = this.lock;
      4     lock.lock();
      5     try {
      6         Object[] elements = getArray();
      7         int len = elements.length;
      8         Object[] newElements = Arrays.copyOf(elements, len + 1);
      9         newElements[len] = e;
     10         setArray(newElements);
     11         return true;
     12     } finally {
     13         lock.unlock();
     14     }
     15 }

    二、Set

    2.1 线程安全问题

           与 List 接口的测试方法相似,同样会抛出 java.util.ConcurrentModificationException 异常。

    2.2 解决办法

      1 // 1. 使用 Collections 工具类封装
      2 Collections.synchronizedSet(new HashSet<>());
      3 
      4 // 2. 使用 java.util.concurrent.CopyOnWriteArraySet;
      5 new CopyOnWriteArraySet<>();

    2.3 CopyOnWriteArraySet

           final ReentrantLock lock = this.lock; 为什么声明为 final?参考可以看看这个 https://blog.csdn.net/zqz_zqz/article/details/79438502

      1 // 底层实际上是一个 CopyOnWriteArrayList
      2 public class CopyOnWriteArraySet<E> extends AbstractSet<E>
      3         implements java.io.Serializable {
      4     private static final long serialVersionUID = 5457747651344034263L;
      5 
      6     private final CopyOnWriteArrayList<E> al;
      7 
      8     // ...
      9 }
      1 // 添加元素,相当于调用 CopyOnWriteArrayList 的 addIfAbsent() 方法
      2 public class CopyOnWriteArraySet<E> {
      3     public boolean add(E e) {
      4         return al.addIfAbsent(e);
      5     }
      6 }
      7 
      8 /**
     9  * CopyOnWriteArrayList 的 addIfAbsent() 方法
     10  * Set 集合中的元素不可重复,如果原集合中有要添加的元素,则直接返回 false
     11  * 否则,将该元素加入集合中
     12  */
     13 public boolean addIfAbsent(E e) {
     14     Object[] snapshot = getArray();
     15     return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
     16         addIfAbsent(e, snapshot);
     17 }
     18 
     19 /**
     20  * 重载的 addIfAbsent() 方法,用于真正添加元素加锁后,再次获取集合,与刚才拿到的集合比较,
     21  * 两次拿到的不一样,说明集合被其他线程修改过了,重新比较最新集合中有没有该元素,如果比较
     22  * 后,没有返回 false,说明没有该元素,执行下面的添加方法。
     23  */
     24 private boolean addIfAbsent(E e, Object[] snapshot) {
     25     final ReentrantLock lock = this.lock;
     26     lock.lock();
     27     try {
     28         Object[] current = getArray();
     29         int len = current.length;
     30         if (snapshot != current) {
     31             // Optimize for lost race to another addXXX operation
     32             int common = Math.min(snapshot.length, len);
     33             for (int i = 0; i < common; i++)
     34                 if (current != snapshot && eq(e, current))
     35                     return false;
     36             if (indexOf(e, current, common, len) >= 0)
     37                 return false;
     38         }
     39         Object[] newElements = Arrays.copyOf(current, len + 1);
     40         newElements[len] = e;
     41         setArray(newElements);
     42         return true;
     43     } finally {
     44         lock.unlock();
     45     }
     46 }
    

    三、Map

    3.1 线程安全问题

           和上面一样,多线程环境下,会抛出 java.util.ConcurrentModificationException 异常。

    3.2 解决办法

      1 // 使用 Collections 工具类
      2 Collections.synchronizedMap(new HashMap<>());
      3 
      4 // 使用 ConcurrentHashMap
      5 new ConcurrentHashMap<>();

    3.3 HashMap、Hashtable 和 ConcurrentHashMap 的区别

    继承不同:HashMap继承AbstractMap, Hashtable继承Dictonary,ConcurrentHashMap除了继承AbstractMap还实现了ConcurrentMap接口

    线程是否安全:HashMap非线程安全,ConcurrentHashMap 和 Hashtable 线程安全,但是他们的实现机制不同,Hashtabl e使用synchronized实现同步方法,而ConcurrentHashMap降低锁的粒度,拥有更好的并发性能。

    Key-Value值:ConcurrentHashMap和Hashtable都不允许value和key为null,但是HashMap允许唯一的key为null,和任意个value为null

    哈希算法不同:HashMap 和 Jdk 8 中的 ConcurrentHashMap 的算法一致都是使用 key 的 hashcode 值进行高16位和低16位异或再取模长度,而Hashtable是直接对 key 的hashcode值进行取模操作 。

    扩容机制不同:ConcurrentHashMap和HashMap的扩容机制和初始容量一致,扩容为原有数组长度的两倍,初始容量为16,但是hashtable中的初始容量为11,容量为原有长度的两倍+1。

    失败机制:ConcurrentHashMap支持安全失败,HashMap和hashtable支持的快速失败

    查询方法:HashMap没有contains方法,但是拥有containsKey和containsValue方法,Hashtable和ConcurrentHashMap还支持contains方法

    迭代方式:ConcurrentHashMap和Hashtable还支持Enumeration迭代方式

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-1 16:39 , Processed in 0.063196 second(s), 30 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表