线程安全详解

news2024/11/16 2:27:10

线程安全

线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。

此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。

正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。

比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。

那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是"线程不安全"了。

线程安全性不是一个非真即假的命题。 Vector 的方法都是同步的,并且 Vector 明确地设计为在多线程环境中工作。但是它的线程安全性是有限制的,即在某些方法之间有状态依赖(类似地,如果在迭代过程中 Vector 被其他线程修改,那么由 Vector.iterator() 返回的 iterator会抛出ConcurrentModificationException)。

对于 Java 类中常见的线程安全性级别,没有一种分类系统可被广泛接受,不过重要的是在编写类时尽量记录下它们的线程安全行为。

Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。只要明确地记录下线程安全特性,那么您是否使用这种系统都没关系。这种系统有其局限性 -- 各类之间的界线不是百分之百地明确,而且有些情况它没照顾到 -- 但是这套系统是一个很好的起点。这种分类系统的核心是调用者是否可以或者必须用外部同步包围操作(或者一系列操作)。下面几节分别描述了线程安全性的这五种类别。

不可变的对象一定是线程安全的,并且永远也不需要额外的同步 。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。

需要注意的是,对于Integer,该类不提供add方法,加法是使用+来直接操作。而+操作是不具线程安全的。这是提供原子操作类AtomicInteger的原因。

线程安全的对象具有在上面"线程安全"一节中描述的属性 -- 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的 -- 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。

有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器 -- 由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的 -- 并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。

如果对一个有条件线程安全类进行记录,那么您应该不仅要记录它是有条件线程安全的,而且还要记录必须防止哪些操作序列的并发访问。用户可以合理地假设其他操作序列不需要任何额外的同步。

线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。

许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。

线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。

这里我们就要提到线程执行的特点

线程执行时多个线程之间会出现抢占的线性 这就注定线程在运行时如果没有限制是很容易出现线程不安全的情况

其次 例如例子中两个顾客同时访问售票处 那么他们都可以进行购票操作对剩余票数进行修改

这在线程中就是两个线程 同时访问同一个变量 这也会使得线程不安全

还有一个线程和另外一个线程之间是互相不可见的 那么就无法相互监控彼此的状态

从而引起上一个问题导致线程不安全

那么关于线程不安全一般都是访问了全局变量或者静态变量引起的

我们要如何处理它呢

处理线程不安全

我们处理线程不安全的最终目的是 不管这个线程如何执行

最终都可以得到同一个结果

那么我们处理线程不安全都有什么办法呢

1.线程同步

线程同步可以让线程内部有序执行 从而避免出现不安全的情况 

关于线程同步

一般有 信号量 条件变量 互斥锁 读写锁来处理线程同步

这样就确保了线程执行多次都可以达到一样的结果

关于线程同步有疑惑的可以翻看我往期博文

线程同步的实现_iccoke的博客-CSDN博客

2.使用线程安全的函数

strtok和strtok_r函数

 

 

我们可以在strtok的函数介绍中可以看到

MT-Unsafe 意思就是这个函数是线程不安全的

因此我们还可以在处理线程不安全时观察代码中是否存在线程不安全的函数

我们可以使用可以实现相同功能的线程安全的函数来替换

从而就可以解决线程不安全 

 3.synchronized

这个关键字可以在线程执行时给线程上锁

实际上这个也是属于线程同步处理线程不安全的情况

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

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

相关文章

【docker11】docker安装常用软件

docker安装常用软件 1.安装软件说明 框架图 总体步骤: 搜索镜像拉去镜像查看镜像启动镜像 - 服务端口映射停止容器移除容器 1.安装tomcat docker hub上查找tomcat镜像 从docker hub上拉取tomcat镜像到本地 docker images查看是否有拉取到的tomcat 使用to…

Spring Security 导致 Spring Boot 跨域失效问题

1、CORS 是什么首先我们要明确,CORS 是什么,以及规范是如何要求的。这里只是梳理一下流程。CORS 全称是 Cross-Origin Resource Sharing,直译过来就是跨域资源共享。要理解这个概念就需要知道域、资源和同源策略这三个概念。域,指…

sahrding-jdbc的雪花算法取模为0或1的问题

工作时无意间发现sahrding-jdbc使用雪花算法生成的id 在某一业务分库分表 永远在那两个库表里面,排查后这里做下分享 环境、配置、问题介绍 16库16表使用的是org.apache.shardingsphere.core.strategy.keygen下面generateKey生成id分库表算法是对16取模生成数据永远在0库0表 0…

如何有效的防护暴力破解和撞库攻击

在网络威胁领域,暴力破解攻击仍然是网络犯罪分子非常喜爱且有利可图的攻击方法。,黑客通过收集互联网已泄露的用户和密码信息,生成对应的字典表,由于许多用户重复使用相同的用户名和密码,攻击者可以使用撞库攻击获得对…

前缀和算法

目录1.概述2.代码实现2.1.一维前缀和2.2.二维前缀和3.应用本文参考: LABULADONG 的算法网站 1.概述 前缀和算法分为一维和二维,一维前缀和可以快速求序列中某一段的和,而二维前缀和可以快速求一个矩阵中某个子矩阵的和。 2.代码实现 2.1.一…

约拍小程序开发,优化约拍产业路径

随着网红、直播经济的发展,年轻群体对于拍摄服务的需求正在急速增长,与此同时该群体用户又极具个性,很多传统影楼拍摄价格高、拍摄风格固定化、等待时间久,个人摄影师宣传推广难度又大,导致拍摄需求被抑制,…

_Linux多线程-基础篇

文章目录1. 什么是线程Linux中的线程叫做轻量级进程(LWP)2. 线程的优点3. 线程的缺点4. 线程异常5. 线程用途6. Linux进程VS线程单进程7. 总结线程在进程内部执行是OS调度的基本单位。不同视角看待进程轻量级进程1. 什么是线程 在一个程序里的一个执行路…

【Cfeng Work】 Open API的intro和 梳理

OpenAPI 开放平台 内容管理Open API intro腾讯云OpenAPIOpenAPI请求API密钥管理云API签名过程拼接规范请求串 CanonicalRequest拼接待签名字符串计算签名拼接到Authorization中云API签名失败规范Token 和 长期密钥公共参数OpenApi设计AppId、AppSecretsign签名timestamp 时间戳…

制造服务行业需要项目管理软件吗?

伴随着时代的快速发展,电子制造服务行业也正在飞速发展,在日新月异得时代步伐下,科学得管理方法和现代化得管理工具相结合,保证企业得进步与发展。项目管理工具通过构造高效统一的项目管理平台,优化企业管理中存在的问…

win10 conda安装labme安装和使用

1、安装conda 在WIN10中配置conda 1.1miniconda下载 https://docs.conda.io/en/latest/miniconda.html 可以任意选择对应的版本,安装时选择配置路径,以免后期重复配置环境。 1.2.2 在环境变量中添加路径 Win R,打开运行,输入…

在ubuntu系统上用pyinstaller加密打包yolov5项目代码的详细步骤

目录0. 背景1. 创建虚拟环境2. pyinstaller打包2.1. 生成并修改spec文件2.2. 重新生成二进制文件3. 测试4. 加密打包4.1. 创建入口函数main.py4.2. 修改detect.py4.3. 加密生成main.spec文件4.4. 修改main.spec文件4.5. 生成二进制文件4.6. 测试0. 背景 最近需要在ubuntu 18.0…

Payso×OceanBase:云上拓新,开启云数据库的智能托管

日前,聚合支付厂商 Payso( 独角鲨北京科技有限公司)的平台、交易系统引入 OceanBase 原生分布式数据库,实现显著降本增效—— 硬件成本降低 20~30%,查询效率提升 80%,执行效率提升了 30%&#x…

学习IB生物,我们需要知道什么知识点?

学习IB课程的很多同学应该都听说过一个说法:IB生物算是理科中的文科,没有公式推导,只有大量需要记忆的内容,不需要用学习理科的思维去学习,其实这种观点是有误区的。实际上,学习生物这门课将会面对的是一个…

01背包问题详解

目录 1.1二维dp数组 1.2一维dp数组改进 1.3相关例题 1.3.1分割等和子集 1.3.2一和零 1.1二维dp数组 概述:背包的最大重量是固定的,物品的数量,重量也是固定的,并且物品只能放一次,可以选择放或者不放&#xff0c…

Redis 核心原理串讲(中),架构演进之高可用

文章目录Redis 核心原理总览(全局篇)前言一、持久化1、RDB2、AOF3、AOF 重写4、混合持久化5、对比二、副本1、同步模式2、部分重同步三、哨兵1、核心能力2、节点通信总结Redis 核心原理总览(全局篇) 正文开始之前,我们…

【FPGA】Verilog:基本实验步骤演示 | 功能电路创建 | 添加仿真激励 | 观察记录仿真波形

前言: 本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载的完整过程、Verilog语言基本运用,电路设计和Test Bench程序的编写、以及实验开发板的使用,通过观察和数据记录理解仿真和FGPA实现的差异。 目录 Ⅰ. 基础知…

考研政治 马原 易混淆知识点

马哲 1. 哲学基本问题 从何者为第一性,分为唯物主义和唯心主义 从是否具有同一性,分为可知论(有同一性)和不可知论(无同一性) 辩证法:联系,发展的观点看世界,认为发展的…

python对接API二次开发高级实战案例解析:百度地图Web服务API封装函数(行政区划区域检索、地理编码、国内天气查询、IP定位、坐标转换)

文章目录前言一、IP定位1.请求URL2.获取IP定位封装函数3.输出结果二、国内天气查询1.请求url2.天气查询封装函数3.输出结果三、行政区划区域检索1.请求url2.区域检索封装函数3.输出结果四、地理编码1.请求url2.地理编码封装函数3.输出结果五、坐标转换1.请求url2.坐标转换封装函…

一文细说Linux虚拟文件系统原理

在 Unix 的世界里,有句很经典的话:一切对象皆是文件。这句话的意思是说,可以将 Unix 操作系统中所有的对象都当成文件,然后使用操作文件的接口来操作它们。Linux 作为一个类 Unix 操作系统,也努力实现这个目标。 虚拟…

CSS 这个就叫优雅 | 多行文本溢出省略

CSS 这个就叫优雅 | 多行文本溢出省略 文章目录CSS 这个就叫优雅 | 多行文本溢出省略一、文本溢出省略方式二、WebKit内核浏览器解决方法🥙三、通用解决方法四、CSS 预处理器封装🥩五、参考资料💘六、推荐博文🍗一、文本溢出省略方…