多线程之常用线程安全类型分析

news2024/10/7 20:27:23

写在前面

本文一起看下在日常工作中我们经常用到的线程安全的数据类型,以及一些经验总结。

1:常用线程安全数据类型

1.1:jdk集合数据类型

jdk的集合数据类型分为两类,一种是线性数据结构,另外一种是字典结构,分别看下。

1.1.1:线性数据结构

线性数据结构主要包括List,Set,Queue,其中List是一种允许数据重复的顺序数据结构,Set是一种不允许数据重复的数据结构,Queue是一种队列的数据结构,接口如下:

public interface List<E> extends Collection<E> {}
public interface Set<E> extends Collection<E> {}
public interface Queue<E> extends Collection<E> {}

二者都继承了相同的集合类结构Collection,如下:

public interface Collection<E> extends Iterable<E> {}

Collection接口🈶继承了jdk rt.jar包中的java.lang.Iterable接口,所以又是支持迭代的。

  • List
    List的主要子类包括ArrayList,LinkedList,Vector,Stack(Vector子类)
  • Set
    Set主要子类有HashSet,LinkedSet,TreeSet
  • Queue
    接口定义如下:
public interface Queue<E> extends Collection<E> {
    // 向队列添加元素,如果是有空间则插入成功,返回true,否则抛出IllegalStateException
    boolean add(E e);

    // 向队列添加元素,如果是有空间则插入成功,返回true,否则返回false(这里不同于add方法抛出异常)
    boolean offer(E e);

    // 获取并且删除队列头元素,如果是队列为空则抛出NoSuchElementException异常
    E remove();

    // 获取并且删除队列头元素,如果是队列为空则返回null(不同于remote抛出NoSuchElementException异常)
    E poll();

    // 获取但是不删除队列头元素,如果是队列为空则抛出NoSuchElementException异常
    E element();

    // 获取但是不删除队列头元素,如果是队列为空则返回null(不同于element抛出NoSuchElementException异常)
    E peek();
}

双向队列子接口Deque,一种双端都可以入队和出队的数据结构,源码如下:

public interface Deque<E> extends Queue<E> {
    // 在deque头添加元素,如果有空间则返回true,否则抛出IllegalStateException异常
    void addFirst(E e);

    // 从deque尾入队,如果成功则返回true,如果空间不足插入失败则抛出IllegalStateException异常
    void addLast(E e);

    // 在deque头添加元素,如果有空间则返回true,否则返回false(优于addFirst抛出IllegalStateException异常)
    boolean offerFirst(E e);

    // 从deque尾入队,如果成功则返回true,如果空间不足插入失败则返回false(优于addLast抛出IllegalStateException异常)
    boolean offerLast(E e);

    // 从deque中获取并且删除头元素,如果dequeu为空则抛出NoSuchElementException异常
    E removeFirst();

    // 获取并且删除deque尾元素,如果是deque没有元素则抛出NoSuchElementException异常
    E removeLast();

    // 从deque中获取并且删除头元素,如果dequeu为空则返回null(不同于removeFirst抛出NoSuchElementException异常)
    E pollFirst();

    // 获取并且删除deque尾元素,如果是deque没有元素则返回null(不同于removeLast抛出NoSuchElementException异常)
    E pollLast();

    // 获取deque头元素,如果deque为空,则抛出NoSuchElementException异常
    E getFirst();

    // 获取deque尾元素,如果deque为空,则抛出NoSuchElementException异常
    E getLast();

    // 获取deque头元素,如果deque为空,则返回null(不同于getFirst抛出NoSuchElementException异常)
    E peekFirst();

    // 获取deque尾元素,如果deque为空,则返回null(不同于getLast抛出NoSuchElementException异常)
    E peekLast();

    // 从deque中删除首次出现的指定元素,如果存在则删除并返回true
    boolean removeFirstOccurrence(Object o);

    // 从deque中删除最后出现的指定元素,如果存在则删除并返回true
    boolean removeLastOccurrence(Object o);

    // *** 队列方法,在父类Queue中已经定义了,这里为什么要重复定义??? ***

    // 在deque尾添加元素,成功返回true,没有可用空间则抛出IllegalStateException异常
    boolean add(E e);

    // 在deque尾添加元素,成功返回true,没有可用空间则返回false(不同于add抛出IllegalStateException异常)
    boolean offer(E e);

    // 从deque获取并且删除首个元素,如果deque为空则抛出NoSuchElementException
    E remove();

    // 从deque获取并且删除首个元素,如果deque为空则返回null(不同于remove抛出NoSuchElementException)
    E poll();

    // 从deque头获取元素,但是不删除,如果是deque为空则抛出NoSuchElementException异常
    E element();

    // 从deque头获取元素,但是不删除,如果是deque为空则返回null(不同于element抛出NoSuchElementException异常)
    E peek();


    // *** 栈相关方法定义 ***

    // 入栈,这里就是将元素添加到deque的头,如果没有可用空间则抛出IllegalArgumentException,该方法同addFirst,但是push方法更加能够对应栈的入栈操作
    void push(E e);

    // 出栈,即获取deque头的元素,如果deque为空则抛出NoSuchElementException异常,该方法同removeFirst()
    E pop();


    // *** 集合方法,看来Deque是一个线性数据结构大杂烩,定义了常见线性数据结构的各种操作 ***

    // 删除首次出现的指定元素,同#removeFirstOccurrence(Object)方法
    boolean remove(Object o);

    // 判断deque中指定元素是否存在
    boolean contains(Object o);

    // 获取deque元素的个数
    public int size();

    // 获取deque对应的迭代器,按照head(first)->tail(last)的顺序获取元素
    Iterator<E> iterator();

    // 获取deque对应的倒叙迭代器,按照tail(last)->head(first)的顺序获取元素
    Iterator<E> descendingIterator();
}

其实LinkedList实现了Deque接口,所以我们不仅可以将LinkedList当做Collection来使用,也可以把其当做Stack,Queue,Deque来使用,如下当做队列Queue使用:

// linkedlist当做queue使用
private static void useAsQueue() {
    Queue<String> queueList = new LinkedList<>();
    // 入队
    queueList.offer("xxxx");
    queueList.offer("yyyy");
    // 出队
    System.out.println(queueList.poll());
    System.out.println(queueList.poll()); // 空了
    System.out.println(queueList.poll());
}

运行结果:
xxxx
yyyy
null

当做Stack使用:

private static void userAsStack() {
    // Deque中定义了stack相关的方法,所以引用使用java.util.Deque
    Deque<String> stack = new LinkedList<>();
    stack.push("aaaa");
    stack.push("bbbb");
    System.out.println(stack.pop());
    System.out.println(stack.pop());
}

运行结果:
[INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ gogogo ---
bbbb
aaaa

1.1.2:字典结构

字典结构相关有两个顶层类是Map(是个接口),Dictionary(是个抽象类),如下:

public interface Map<K,V> {}
public abstract class Dictionary<K,V> {}

分别看下。

  • Map
    主要子类HashMap,LinkedHashMap,TreeMap。
  • Dictionary
    主要子类HashTable,Properties,其中Properties是HashTable的子类,另外Properties有个需要小心,即如果是value是int时,可以插入数据,但是当get时会返回null,如下测试:
public static void main(String[] args) {
//        useAsQueue();
//        userAsStack();
    Properties p = new Properties();
    p.put("name", "jack");
    p.put("age", 90);
    System.out.println(p.getProperty("name"));
    System.out.println(p.getProperty("age"));
}

运行结果:
jack
null

如果不小心掉到了properties的这个坑里还真是不好发现😭😭😭。

1.2:List分析

不管是ArrayList还是LinkedList,都不是线程安全的数据结构,存在线程安全问题:

1:写写冲突
    当多个线程并发写时可能会出现数据覆盖的问题,比如线程A和线程B都修改位置2的元素,最终到底是谁设置成功就不一定了,特别是对于+1场景会导致少1
2:读写冲突
    当读时写可能会产生不可预期的后果,当出现读的过程中发现元素个数发生变化的情况,将会抛出ConcurrentModificationException异常。

那么,如何实现List的线程安全呢?分别来看下。

1.2.1:使用Vector

Vector通过synchronized关键字对方法上对象锁,实现了线程安全,即串行执行,效率堪忧。该方法也是List接口的子类,方法签名如下:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}

1.2.2:使用Collections.synchronizedXXX方法

在这里插入图片描述

使用Collections.synchronizedXXX方法转换为线程安全的集合对象,其实就是对其进行简单包装,方法加上synchronized关键字,所以其本质上通Vector,以synchronizedList为例:

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

返回的是SynchronizedList包装类,该类方法如下:

public E set(int index, E element) {
    synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
    synchronized (mutex) {return list.remove(index);}
}

public int indexOf(Object o) {
    synchronized (mutex) {return list.indexOf(o);}
}

可以看到都是使用synchronized关键字将具体集合方法的调用放在了同步代码块中。

1.2.3:使用Arrays.asList

该方法返回的是Arrays内部类ArrayList,而非java.util.ArrayList,方法仅仅实现了读取操作和set操作,因此如果是调用add,remove等方法将会调用到AbstractList,会抛出UnsupporedOperationException,如下:

// java.util.Arrays.ArrayList
private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {}

测试代码:
List<String> list = Arrays.asList("aa", "bb");
list.add("ccc");

运行结果:
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at dongshi.daddy.Huohuo.main(Huohuo.java:17)

从调用栈也可以看出该异常是AbstractList抛出的,其实就是如下方法:

// java.util.AbstractList#add(int, E)
public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

1.2.4:使用Collections.unmodifiableXxxx

在这里插入图片描述

Collections.unmodifiableXxxx方法会返回一个包装类,该类只允许查看数据,不允许更新数据,也不允许排序,因为排序本质上也是一种修改数据的操作,以unmodifiableList为例如下:

public static <T> List<T> unmodifiableList(List<? extends T> list) {
    return (list instanceof RandomAccess ?
            new UnmodifiableRandomAccessList<>(list) :
            new UnmodifiableList<>(list));
}

返回的包装类是UnmodifiableList,该类针对修改相关的操作统一抛出UnsupportedOperationException,如下:

public E set(int index, E element) {
    throw new UnsupportedOperationException();
}
public void add(int index, E element) {
    throw new UnsupportedOperationException();
}
public E remove(int index) {
    throw new UnsupportedOperationException();
}
...
public boolean addAll(int index, Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public void replaceAll(UnaryOperator<E> operator) {
    throw new UnsupportedOperationException();
}
@Override
public void sort(Comparator<? super E> c) {
    throw new UnsupportedOperationException();
}

1.2.5:使用CopyOnWriteArrayList

在这里插入图片描述

使用COW,即写时复制方式实现线程安全的集合类,在读时不锁,写时写锁,本事上是一种读写分离思想的运用,读取数据是最终一致的,看下修改方法:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 重入锁上锁
    lock.lock();
    try {
        // 获取当前的数组
        Object[] elements = getArray();
        int len = elements.length;
        // 拷贝原数组并将长度+1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 添加新元素
        newElements[len] = e;
        // 这里设置到private transient volatile Object[] array;注意其是volatile的,根据“对volatile变量的写操作 先行发生于 对volatile的读操作“,后续线程将直接能够读到这里设置的最新值
        setArray(newElements);
        return true;
    } finally {
        //  解锁
        lock.unlock();
    }
}

迭代器的实现也是使用快照,如下:

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        // 这里虽然是引用传递,但是因为如果是原集合被修改,通过COW会创建新数组,所以不会改变这里的值
        // ,即不会破坏这里的快照
        cursor = initialCursor;
        snapshot = elements;
    }
}

适合场景:读锁写少

1.3:Map分析

1.3.1:HashMap

初始容量16,扩容*2,负载因子0.75,jdk8引入红黑树解决hash冲突,当冲突链长度到8,数组长度到64后,升级冲突链表为红黑树。非线程安全,可能如下线程安全问题:

1:读写冲突
2:扩容数据读取导致死循环(严重)
3:keys无序问题

其中2是存在扩容节点重分布导致出现两个节点互为next的情况,进而导致CPU,耗尽CPU资源。3keys方法获取键是通过如下方式循环获取的:

在这里插入图片描述

因此在扩容,节点重分布的过程中导致错误。

1.3.2:LinkedHashMap

继承自HashMap,如下:

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{}

在HashMap基础上增加了链表结构维护数据插入的先后顺序,但也有线程安全问题。

1.3.3:ConcurrentHashMap

在juc 中提供的HashMap的线程安全版本,在jdk7中使用分段的方式来实现线程安全,如下:

在这里插入图片描述

在这里插入图片描述

jdk7中虽然通过Segment使加锁的概率降低,但是还是有锁,在jdk8中使用基于cas的乐观锁技术进行改版,如下:

在这里插入图片描述

2:其它知识点

2.1:ThreadLocal

在一个线程内传递变量的机制,每个线程独立,通过set方法设置变量,通过get方法获取变量。

在这里插入图片描述

2.2:并行stream

通过添加.parallel以并行的方式来执行集合操作,底层使用的是JUC提供的多线程实现相关功能,如下:

在这里插入图片描述

2.3:加锁需要考虑的问题

1:锁的粒度如何控制
2:使用公平锁还是非公平锁
3:是否需要考虑自旋的情况
4:是否需要考虑重入
5:当前场景适合是用什么锁,适合如何加锁(脱离场景讨论问题都是耍流氓)
6:加锁后是否会严重影响程序性能

2.4:线程间通信

1:Thrad.join
2:Object,wait/notify/notifyAll
3:JUC工具类
    Semaphore,CountDownLatch,CyclicBarrier

写在后面

参考文章列表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/679437.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【系统架构】第五章-软件工程基础知识(一)

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 软件工程 一、软件过程模型 1、瀑布模型 特点&#xff1a;前一个阶段工作的输出结果&#xff0c;是后一个阶段工作的输入 缺点&#xff1a; 软件需求的完整性、正确性等很难确定&#xff0c…

人力资源外包系统(社会实践管理系统)需求分析文档

该系统主要针对当前在校大学生、中专院校学生寒、暑假社会实践而设计的管理系统&#xff1b; 主要用户群为人力资源外包公司&#xff0c;也可以是各个学校管理学生使用&#xff1b; 主要功能有维护企业信息&#xff0c;招工信息&#xff0c;企业宣传资料&#xff1b;维护学校…

基于JavaSpringBoot+uniapp制作一个记账小程序

你不理财,财不理你,制作一个记账小程序对自己的收入/支出明细进行管理,守护好自己的钱袋子。 一、小程序1.1 项目创建1.2 首页1.3 收支报表页1.4 记账提交页1.5 记账列表页

NCI Core Framework

3.1 概述 NCI 核心包括以下必需的功能&#xff1a;  通过 NCI 传输命令、响应、通知和数据消息的数据包格式。  用于设备主机和 NFC 控制器之间不同操作&#xff08;第 4 节中指定&#xff09;的命令、响应和通知的定义。 &#xff08;本规范后面的一些部分定义了不属于…

一起学SF框架系列4.7-模块context-MessageResource

Spring通过MessageSource接口提供了国际化&#xff08;“i18n”&#xff09;功能。搞明白MessageResource相关接口和类&#xff0c;基本就掌握了国际化功能。 MessageSource接口 定义了消息访问接口。 String getMessage&#xff08;String code&#xff0c;Object[]args&…

全网唯一!Matlab原神配色包MGenshin

前段时间&#xff0c;在原神官网逛了一圈&#xff0c;发现里面角色的原画配色十分的好看&#xff0c;便突发奇想&#xff1a;要是把原神配色用在SCI论文插图上&#xff0c;emmmmm…… 于是&#xff0c;我默默打开了自己的Matlab&#xff0c;用TheColor工具箱的图片主题色提取功…

数据驱动的商业决策:BI在企业中的重要性

第一章&#xff1a;引言 在当今数字化时代&#xff0c;数据被广泛应用于各个行业和领域。对企业而言&#xff0c;数据是一项宝贵的资源&#xff0c;可以为其带来无限的商机和竞争优势。然而&#xff0c;海量的数据本身并不具备实际意义&#xff0c;只有通过有效的数据分析和洞…

亚马逊云科技中国峰会:Amazon DeepRacer-梦想加速度

零&#xff1a;前言 你是否憧憬在赛车赛道上开车&#xff0c;享受疾驰而过的感觉&#xff0c;感受无与伦比的驾驶乐趣&#xff1f; Amazon DeepRacer可以满足你的梦想&#xff0c;虚竹哥带你了解Amazon DeepRacer。 它为用户提供了一个虚拟仿真环境和一个真实赛车模型&#xf…

【JDK环境配置】| 两种JDK环境能在同一台电脑共存吗?(文末送书)

目录 &#x1f981; 前言&#x1f981; 基础环境&#x1f981; 安装JDK1.8Ⅰ. 下载Ⅱ. 安装 &#x1f981; 在项目里更改JDK版本---------------------------------------------福利在下面--------------------------------------------------&#x1f981; 福利&#xff08;送…

《基于同态加密和秘密分享的纵向联邦LR协议研究》论文阅读

论文地址&#xff1a;https://xueshu.baidu.com/usercenter/paper/show?paperid1b7e04e0r41x0ax0976q0gy0m5242465 摘要 提出了一种新颖的两方纵向联邦逻辑回归协议,并在半诚实安全模型下证明了该协议的安全性, 包括模型训练流程和模型推理流程的安全性,且无需对非线性函数使…

C++四种基本类型转换

C四种基本类型转换 1.static_cast2.const_cast3.reinterpret_cast4 .dynamic_cast 1.static_cast 用法: static_cast<type_name> (expression) 说明:该运算符把expression转换为typen_name类型&#xff0c;static_cast在编译时使用类型信息执行转换,在转换执行必要的检测…

spring事务源码详解-spring原码(一)

前面说过了aop源码&#xff0c;这里再稍微回顾一下 我们会用注解EnableAspectJautoProxy开启aop 当我们用了proxytargetClass会强制cglib动态代理 源码里有Import 里面会注册AnnotionAwareAspectJAutoProxyCreator 后面会在beanDefinationMap获取到所有定义的Objects循环 …

2020新基建决赛-crypto-onepiece

onepiece 一、概要 1、标题&#xff1a;onepiece 2、关键字&#xff1a;e2&#xff0c;rabin&#xff0c;凯撒 3、比赛&#xff1a;2020新基建决赛 4、工具&#xff1a;python&#xff0c;米斯特工具 二、开始 1、题目分析 题目给了一个pubkey.pem和onepiece.enc&#xff…

C++右值引用 移动语义 完美转发 引用叠加

右值引用 MyString浅拷贝与深拷贝浅赋值与深赋值 左值与右值左值概念左值右值与函数的结合移动构造函数移动赋值函数移动构造和移动赋值的应用 移动语义 有点问题完美转发引用叠加 MyString 浅拷贝与深拷贝 s1先在堆区申请了空间&#xff0c;然后将p指针指向的字符串复制到该…

设计模式之责任链模式笔记

设计模式之责任链模式笔记 说明Chain of Responsibility(责任链)目录责任链模式示例类图请假条类抽象处理者类小组长类部门经理类总经理类测试类 说明 记录下学习设计模式-责任链模式的写法。JDK使用版本为1.8版本。 Chain of Responsibility(责任链) 意图:使多个对象都有机…

Vue搜索组件,显示热门、近期搜索(结合element ui)

&#x1f680; 注重版权&#xff0c;转载请注明原作者和原文链接 &#x1f96d; 作者&#xff1a;全栈小袁 &#x1f34e; 原创个人开源博客项目(目前V3.0版本)&#xff1a;https://github.com/yuanprogrammer/xiaoyuanboke &#x1f349; 开源项目觉得还行的话点点star&#x…

【P4】Windows 下搭建 DVWA 及命令注入漏洞详解

文章目录 一、Windows 下搭建 DVWA1.1、DVWA 靶场搭建1.2、六步快速搭建 DVWA1.2.1、下载并安装 PHPstudy&#xff1a;http://public.xp.cn/upgrades/PhpStudy2018.zip1.2.2、将解压后的 DVWA 原代码放置 phpstudy 安装目录的 WWW文件夹1.2.3、进入 DVWA/config 目录&#xff0…

2022前端趋势报告(下)

前端博主&#xff0c;热衷各种前端向的骚操作&#xff0c;经常想到哪就写到哪&#xff0c;如果有感兴趣的技术和前端效果可以留言&#xff5e;博主看到后会去代替大家踩坑的&#xff5e; 主页: oliver尹的主页 格言: 跌倒了爬起来就好&#xff5e; 一、前言 本文内容来自于《St…

springBoot学习——spring+springMVC 集成mybatis 拦截器

目录 引出入门案例&#xff1a;登陆和注册 & 用户信息分页 之 固定的步骤&#xff1a;&#xff08;1&#xff09;建普通项目配置pom.xml文件&#xff08;2&#xff09;写主启动类 application.yml文件【bug】pom.xml文件导了mybatis的包&#xff0c;但是application.yml文…

Drag Your GAN论文解读,基于点的交互式操作拖动到生成图像[DragGAN]

只需要鼠标的点击就可以自动修图的产品&#xff0c;火爆问世&#xff0c;可以说是超越PS&#xff0c;神一般的存在了&#xff0c;而且没有门槛&#xff0c;对于普通大众来说直接可以上手使用&#xff0c;这个是PS完全不具备的。更关键的是&#xff0c;这款产品跟PS明显区别在于…