【JUC】三、集合的线程安全

文章目录

1、ArrayList集合线程安全问题分析

对List集合非线程安全的Demo代码:

java 复制代码
public class ArrayListDemo {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        //多个线程同时写入List集合
        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();
        }
    }
}

运行:

ConcurrentModificationException异常,是在多线程环境下,当一个线程正在遍历集合,而另一个线程对集合进行了修改操作时,就会抛出这个异常。以ArrayList为例,其add方法源码,未加synchronized关键字:

再点击报错详情,进入抛出异常的方法:

modCount即集合新增的次数,是实际修改次数,而expectedModCount是预期修改次数,它是ArrayList的一个内部类Itr的成员变量,调用iterator()获取迭代器时,内部创建Itr对象,此时,modCount会赋值给expectedModCount:

拿到迭代器对象,要遍历集合时,modCount已经赋值给expectedModCount,而此时其他线程继续add,modCount+1,modCount和expectedModCount就不相等了。

2、解决方式一:Vector或synchronizedList( )

List接口的另一个实现类Vector,其add方法加了关键字,使用它可解决线程安全问题,但很古老了,since1.2,很少用了。

java 复制代码
List<String> list = new Vector<>();
//重复代码略

同样一种古老的解决方案,可以用Collections的synchronizedList方法,传入一个有线程安全问题的List,如ArrayList:

java 复制代码
List<String> list = Collections.synchronizedList(new ArrayList<>());

3、解决方式二:CopyOnWriteArrayList 写时复制

java 复制代码
List<String> list = new CopyOnWriteArrayList<>();

完整demo:

java 复制代码
public class ArrayListDemo {

    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        //多个线程同时写入List集合
        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();
        }
    }
}

原理是写时复制技术,即:

  • 对这个List实现类的集合,可以多线程并发读
  • 往集合中写的时候,则只能独立写,先复制一份原来的集合,这个时候读还是读原来的,然后往新集合里面写入新的内容
  • 写完后新旧合并,再读时,就读这个合并后的集合

看下源码,再对照着理解写时复制:

4、HashSet集合线程不安全的分析与解决

java 复制代码
public class HashSetDemo {

    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        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();
        }
    }
}

运行:

解决办法类比上面的List,使用CopyOnWriteArraySet

java 复制代码
Set<String> set = new CopyOnWriteArraySet<>();

5、HashMap集合线程不安全的分析与解决

java 复制代码
public class HashSetDemo {

    public static void main(String[] args) {
        Map<String,string> map = new HashMap<>();
        for (int i = 0; i < 30; i++) {
        	String key = String.valueOf(i);
            new Thread(() -> {
                //写入
                map.put(key,UUID.randomUUID().toString().substring(0,8));
                //读
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

解决办法类比List,用ConcurrentHashMap

java 复制代码
Map<String,String> map = new ConcurrentHashMap<>();
相关推荐
_OP_CHEN18 小时前
【Linux系统编程】(四十七)线程安全与线程锁深度解析:从概念到实战,避坑指南全掌握
linux·操作系统·线程池·进程·线程安全·c/c++·线程锁
_OP_CHEN1 天前
【Linux系统编程】(四十六)线程池原理与实现:从固定线程池到线程安全单例模式
linux·单例模式·操作系统·线程池·进程·线程安全·c/c++
西门吹雪分身16 天前
JUC之公平锁与非公平锁
java·并发·juc·
西门吹雪分身16 天前
JUC之可重入锁
java·juc·死锁·公平锁·非公平锁
长安城没有风1 个月前
Java 高并发核心编程 ----- 线程池原理与实践(上)
java·juc
egoist20231 个月前
[linux仓库]线程池(单例模式)、线程安全与重入、死锁[线程·拾]
linux·单例模式·饿汉模式·懒汉模式·线程安全·死锁·重入问题
长安城没有风2 个月前
Java 高并发核心编程 ----- 初识多线程(下)
java·juc
Chan162 个月前
《Java并发编程的艺术》| ConcurrentHashMap 在 JDK 1.7 与 1.8 的底层实现
java·spring boot·java-ee·intellij-idea·juc
Chan162 个月前
《Java并发编程的艺术》| 并发关键字与 JMM 核心规则
java·开发语言·数据库·spring boot·java-ee·intellij-idea·juc
长安城没有风2 个月前
Java 高并发核心编程 ----- 初识多线程(上)
java·juc