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

相关推荐
飞滕人生TYF23 分钟前
java 排序 详解
java·算法·排序算法·方法
Java小王子呀1 小时前
java使用itext生成pdf
java·pdf
JWASX1 小时前
定时/延时任务-Timer用法
java·定时器·timer
大风吹PP凉1 小时前
45系统调用与内核API
java·linux·服务器
as_jopo1 小时前
-Dspring.profiles.active=dev与--spring.profiles.active=dev的区别
java·后端·spring
hummhumm1 小时前
第 32 章 - Go语言 部署与运维
java·运维·开发语言·后端·python·sql·golang
QQ_1154320311 小时前
基于Java+SpringBoot+Mysql在线简单拍卖竞价拍卖竞拍系统功能设计与实现四
java·spring boot·mysql·毕业设计·毕业源码·竞拍系统·竞拍平台
fa_lsyk1 小时前
Spring:AOP面向切面案例讲解AOP核心概念
java·后端·spring
陈奕迅本讯1 小时前
人力资源项目学习
java·学习
2401_878467321 小时前
大连环保公益管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·学习·tomcat·maven