集合类不安全

List不安全

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

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循环开更多线程

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关键字来保证线程安全。

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。

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

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

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解决方案

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

JUC并发编程学习(五)集合类不安全的更多相关文章

  1. JUC并发编程学习笔记

    JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...

  2. [Java并发编程(五)] Java volatile 的实现原理

    [Java并发编程(五)] Java volatile 的实现原理 简介 在多线程并发编程中 synchronized 和 volatile 都扮演着重要的角色,volatile 是轻量级的 sync ...

  3. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  4. 并发编程学习笔记(8)----ThreadLocal的使用及源码分析

    1. ThreadLocal的理解 ThreadLocal,顾名思义,就是线程的本地变量,ThreadLocal会为每个线程创建一个本地变量副本,使得使用ThreadLocal管理的变量在多线程的环境 ...

  5. 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理

    (一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...

  6. 并发编程学习笔记(4)----jdk5中提供的原子类及Lock使用及原理

    (1)jdk中原子类的使用: jdk5中提供了很多原子类,它会使变量的操作变成原子性的. 原子性:原子性指的是一个操作是不可中断的,即使是在多个线程一起操作的情况下,一个操作一旦开始,就不会被其他线程 ...

  7. Java并发编程学习前期知识下篇

    Java并发编程学习前期知识下篇 通过上一篇<Java并发编程学习前期知识上篇>我们知道了在Java并发中的可见性是什么?volatile的定义以及JMM的定义.我们先来看看几个大厂真实的 ...

  8. 并发编程(五)LockSupport

    并发编程(五)LockSupport LockSupport 提供 park() 和 unpark() 方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于"许可(permit)&qu ...

  9. 并发编程学习笔记(15)----Executor框架的使用

    Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...

  10. 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理

    1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...

随机推荐

  1. Redis的设计与实现-总结

    个人真的很喜欢这本书, 从对C语言一窍不通, 到发现C语言竟然如此简洁, 以至于我喜欢上了C! 对此前面的底层数据结构也读了几次, 大致整理了书里的内容, 后面的就粗略看了一下, 不再细细整理了. R ...

  2. 利用InnoStep在VS编译时自动构建安装包

    摘要 很多同学在C/S开发领域或多或少都可能会遇到需要制作安装包的场景,打包的工具也是五花八门,例如有NSIS.InstallShield.Wix Toolset.ClickOnce等等,这里以Inn ...

  3. 私网部署DNS(BIND)笔记

    准备工作 下载 yum install -y bind bind-utils包含dig.nslookup等调试命令,非必须. yum install -y bind-utils 防火墙 firewal ...

  4. PaddleSharp:跨越一年的版本更新与亮点

    PaddleSharp:跨越一年的版本更新与亮点 我始终坚信,开源社区是技术进步的重要推动力,也是我抽出我业余时间,投入到PaddleSharp这个项目的原因,这个项目充分展现了.NET在复杂计算领域 ...

  5. redis 中的 list

    lpush K1 V1 V2 V3   左边加入list rpush k1 v1 v2 v3 右边加入list lpop k1 左边吐出一个值 rpop k1 右边吐出一个值 lrange k1 0 ...

  6. 如何在CMD窗口运行python文件

    进入文件所在的路径输入: python  文件名

  7. Unity的BuildPlayerProcessor:深入解析与实用案例

    Unity BuildPlayerProcessor Unity BuildPlayerProcessor是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目时自动执行一些操作.这个功能可 ...

  8. .Net Core 3.0 对 MongoDB 的多条件查询(两种)操作

    前言   在日常开发中,偶尔会用到 MongoDB 的数据操作,也花费了一些时间调试,因此在此处记录一下,共同进步. 废话少说,出招吧! 正文 2.1 准备工作 首先需要引入 .Net 平台链接 Mo ...

  9. [nginx]反向代理grpc

    前言 nginx从1.13.10版本开始提供对gRPC代理的支持.由于grpc基于http2,因此编译nginx时需要添加参数--with-http_v2_module来启用对http2协议的支持. ...

  10. iptables基础

    简介 Linux防火墙主要工作在网络层,针对TCP/IP数据包实施过滤和限制.iptables是linux防火墙的管理程序,为包过滤机制的实现制定规则,并告诉内核空间的netfilter如何处理这些网 ...