JUC并发编程学习(五)集合类不安全

集合类不安全

List不安全

单线程情况下集合类和很多其他的类都是安全的,因为同一时间只有一个线程在对他们进行修改,但是如果是多线程情况下,那么集合类就不一定是安全的,可能会出现一条线程正在修改的同时另一条线程启动来对这个集合进行修改,这种情况下就会导致发生并发修改异常**(在jdk11的环境下多次测试该代码发现并无问题,但是学习教程中有该异常。原因:线程数量不够)**

java 复制代码
package org.example.unsafe;

import java.util.ArrayList;
import java.util.UUID;

public class Test1 {
    public static void main(String[] args) {
        ArrayList<String> sts = new ArrayList<>();

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


    }
}

重现该异常,通过for循环开更多线程

java 复制代码
package org.example.unsafe;

import java.util.ArrayList;
import java.util.UUID;

public class Test1 {
    public static void main(String[] args) {
        MidiFireList midiFireList = new MidiFireList();


        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                midiFireList.midi();
            }, "A").start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                midiFireList.midi();
            }, "B").start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                midiFireList.midi();
            }, "C").start();
        }


    }

}

class MidiFireList {
    ArrayList<String> sts = new ArrayList<>();

    public void midi() {
        sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(sts);
    }
}

成功重现异常

解决List的并发修改异常

1、通过使用List的子类Vector来操作,Vector默认时线程安全的,所以不会出现以上情况,Vector时jdk1.0时期就出现的,它的add方法使用了synchronized关键字来保证线程安全。

java 复制代码
class MidiFireList {
    //使用了线程安全的Vector集合类
    List<String> sts = new Vector<>();

    public void midi() {
        sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(sts);
    }
}

2、通过所有集合的父类Collections类的线程安全的方法创建一个ArraryList。

java 复制代码
class MidiFireList {
    List<String> sts = Collections.synchronizedList(new ArrayList<String>());

    public void midi() {
        sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(sts);
    }
}

3、通过JUC包下的CopyOnWriteArrayList类来创建一个ArrayList,他内部的方法通过同步代码块和lock锁实现了线程安全的各种操作,缺点时少量线程操作时成本太高(CopyOnWrite写入时复制,COW思想,是计算机程序设计领域中的一种优化策略),在写入时复制一份,避免覆盖导致数据问题,读写分离思想

CopyOnWriteArrayList和Vector的在线程安全方面的区别,为什么要用CopyOnWriteArrayList

CopyOnWriteArrayList对比Vector,我们可以通过源码来看

CopyOnWriteArrayList:

Vector:

jdk1.8时的CopyOnWriteArrayList:

其实在jdk11之后的区别只在于同步代码块和同步方法的区别,可参考同步代码块和同步方法有什么区别 • Worktile社区瞄一眼CopyOnWriteArrayList(jdk11) - 傅晓芸 - 博客园 (cnblogs.com)这两篇文章。

但是在jdk1.8时,CopyOnWriteArrayList的方法时单纯的通过Lock锁来实现同步的,没有使用synchronized关键字,因为会影响性能。

Set不安全

Set的不安全问题与List一样,解决方案如下

1、通过Collections的同步方法来创建一个线程安全的Set

java 复制代码
class MidiFireList {
    Set<String> set = Collections.synchronizedSet(new HashSet<>());

    public void midi() {
        set.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(set);
    }
}

2、通过CopyOnWriteArraySet类来创建线程安全的Set

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

    public void midi() {
        set.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(set);
    }
}

HashSet的底层就是HashMap,他就不是一个新的东西

HashSet的add方法就时HashMap的put方法封装了一下

map的key是无法重复的,所以HashSet是无序的

Map不安全

Map解决方案

java 复制代码
package org.example.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest {
    public static void main(String[] args) {
//        HashMap是这样用的吗?不是工作中不用HashMap
//        默认等价于什么? new HashMap<>(16,0.75);
        Map<String, String> map = new ConcurrentHashMap<>();
//        加载因子、初始化容量

        for (int i = 0; i < 50; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

    }
}

注意Map的并发类为ConcurrentHashMap

相关推荐
程序媛小果几秒前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林6 分钟前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨20 分钟前
El表达式和JSTL
java·el
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
九圣残炎3 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode