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

相关推荐
7澄1几秒前
深入解析 LeetCode 数组经典问题:删除每行中的最大值与找出峰值
java·开发语言·算法·leetcode·intellij idea
ysyxg5 分钟前
设计模式-策略模式
java·开发语言
Felix_XXXXL16 分钟前
Spring Security安全框架原理与实战
java·后端
一抓掉一大把37 分钟前
秒杀-StackExchangeRedisHelper连接单例
java·开发语言·jvm
升鲜宝供应链及收银系统源代码服务37 分钟前
升鲜宝生鲜配送供应链管理系统--- 《多语言商品查询优化方案(Redis + 翻译表 + 模糊匹配)》
java·数据库·redis·bootstrap·供应链系统·生鲜配送·生鲜配送源代码
青山的青衫1 小时前
【JavaWeb】Tlias后台管理系统
java·web
蒟蒻的工具人1 小时前
SSE实时推送订单状态
java·eventsource·sse协议
小蒜学长1 小时前
springboot基于Java的校园导航微信小程序的设计与实现(代码+数据库+LW)
java·spring boot·后端·微信小程序
王元_SmallA1 小时前
IDEA + Spring Boot 的三种热加载方案
java·后端
小苏兮1 小时前
【把Linux“聊”明白】编译器gcc/g++与调试器gdb/cgdb:从编译原理到高效调试
java·linux·运维·学习·1024程序员节