编写线程安全代码的核心是管理对状态的访问,尤其是对共享、可变状态的访问

编写线程安全代码的核心是管理对状态的访问,尤其是对共享、可变状态的访问。

Writing thread-safe code is, at its core, about managing access to state, and in particular to shared, mutable state.

一、ExecutorService使用中execute()和submit()

实践发现:

  • execute执行方式抛出异常显示在控制台了
  • submit执行方式啥都没有输出。

线程池在处理任务时通常不会直接抛出异常,因为它主要用于调度和执行任务,而不是验证参数的有效性。

对于 execute() 方法,如果任务在执行过程中抛出异常并且没有被显式捕获,线程池会将异常记录到内部的 UncaughtExceptionHandler,并在控制台上输出异常的堆栈跟踪信息。

对于 submit() 方法,由于它返回一个 Future 对象,任何从任务中抛出的异常都会被包装在 Future 对象中。如果你不主动调用 Future.get() 方法获取结果,异常将不会被抛出和显示。如果你调用了 Future.get() 并且任务抛出了异常,那么 get() 方法将会抛出 ExecutionException,其中包含真实的异常信息。这意味着你可以通过 Future.get() 方法来检查任务是否成功完成,并获取异常信息(如果有)。

两者优缺点

execute()submit() 方法都可以用于向线程池提交任务,但它们在使用场景、优势和劣势方面有一些区别。

execute() 方法主要用于提交实现了 Runnable 接口的任务,而 submit() 方法可以提交实现了 RunnableCallable 接口的任务。

下面是两者的优势和劣势:- **`execute()` 的优势:**1. 简洁:`execute()` 方法比较简洁,不需要返回结果,适合于不关心任务的返回结果,只需简单执行任务的场景。
2. 异常处理:`execute()` 方法会直接将任务内部抛出的异常显示在控制台上,方便调试和排查问题。- **`execute()` 的劣势:**1. 无法获取任务执行的结果:`execute()` 方法没有返回值,无法获取任务的执行结果。如果需要获取任务结果或者判断任务是否成功完成,就无法使用 `execute()` 方法。
2. 任务的异常处理比较困难:由于没有返回值,我们需要在任务内部进行异常处理,并且需要对异常进行特殊处理,例如将异常记录到日志中。- **`submit()` 的优势:**1. 可以获取任务执行结果:`submit()` 方法返回一个 `Future` 对象,可以通过 `Future` 对象获取任务执行的结果。这对于需要获取任务结果、进行后续处理或者判断任务是否成功完成的场景非常有用。
2. 异常处理方便:通过调用 `Future.get()` 方法,可以捕获任务内部抛出的异常,并对异常进行处理。
3. 支持提交 `Callable` 任务:`submit()` 方法除了支持提交 `Runnable` 任务外,还可以提交实现了 `Callable` 接口的任务,这样可以获取任务执行的结果。- **`submit()` 的劣势:**1. 稍微复杂:相比于 `execute()` 方法,`submit()` 方法稍微复杂一些,需要处理 `Future` 对象的返回值和异常。

综上所述,如果你只是简单地希望执行任务而不关心任务的返回结果,或者你可以在任务内部处理异常,并且方便地在控制台上查看异常信息,那么使用 execute() 方法是一个不错的选择。但是,如果你需要获取任务执行的结果、进行后续处理,并且更灵活地处理任务内部的异常,那么使用 submit() 方法会更合适。

①关闭与阻塞

@Override
public void close() throws Exception {shutdown();//停止线程池接受新的任务,并尝试将已经提交的但还未开始执行的任务取消。awaitTermination();//阻塞当前线程直到线程池中的所有任务执行完成或超时。
}

通过在 close() 方法中先调用 shutdown() 再调用 awaitTermination(),可以确保在关闭线程池之前等待任务执行完成。这样,线程池中的任务就有足够的时间来完成执行,避免了任务被提前取消的情况发生。

二、锁修饰范围、锁住什么(锁的粒度)、互斥范围、优化锁的使用

①synchronized(this)、synchronized(class)与synchronized(Object)的区别

`synchronized` 关键字用于实现线程的同步,确保多个线程在访问共享资源时按照一定的顺序进行执行。在 Java 中,你可以使用 `synchronized` 关键字来实现对对象、类或代码块的同步。下面是 `synchronized(this)`、`synchronized(class)` 和 `synchronized(Object)` 的区别:1. `synchronized(this)`
`synchronized(this)` 用于同步一个对象的实例方法或实例代码块。它锁住的是当前对象的实例。当一个线程获得了这个锁之后,其他试图访问该对象实例中被 `synchronized(this)` 保护的代码块或方法的线程将会被阻塞,直到获得锁的线程释放锁。2. `synchronized(class)`
`synchronized(class)` 用于同步一个类的静态方法或静态代码块。它锁住的是整个类的实例,而不是特定的对象实例。因此,当一个线程获得了 `synchronized(class)` 锁之后,其他试图访问该类中被 `synchronized(class)` 保护的静态代码块或方法的线程也将被阻塞,直到获得锁的线程释放锁。需要注意的是,`synchronized(class)` 会影响整个类的所有实例,而不管是哪个实例调用了 `synchronized(class)` 所保护的方法或代码块,因此需要谨慎使用。3. `synchronized(Object)`
`synchronized(Object)` 用于同步一个特定的对象实例。可以使用任意对象作为锁对象,比如一个专门用于同步的对象(通常称为监视器对象)。当一个线程获得了 `synchronized(Object)` 锁之后,其他试图访问使用相同锁对象的 `synchronized(Object)` 保护的代码块或方法的线程将会被阻塞,直到获得锁的线程释放锁。不同的线程可以使用不同的锁对象进行同步,这样它们之间的同步操作不会互相干扰。需要注意的是,如果多个线程使用了不同的锁对象,那么它们之间的同步将失效,因为它们没有竞争同一个锁。综上所述,`synchronized(this)` 和 `synchronized(Object)` 都是针对特定对象实例进行同步的,而 `synchronized(class)` 是锁住整个类的所有实例。选择使用哪种方式取决于你的需求和设计。

②锁修饰对象包括:

synchronized 关键字可以修饰以下几个对象:

  1. 实例方法:使用 synchronized 修饰实例方法时,该方法在同一时间只能被一个线程访问。当一个线程正在执行该实例方法时,其他线程需要等待。
public synchronized void instanceMethod() {// 方法体
}
  1. 静态方法:使用 synchronized 修饰静态方法时,该方法在同一时间只能被一个线程访问。和实例方法一样,当一个线程正在执行该静态方法时,其他线程需要等待。
public static synchronized void staticMethod() {// 方法体
}
  1. 代码块:使用 synchronized 修饰代码块时,可以指定加锁的对象。多个线程在同一时间只能有一个线程执行被 synchronized 修饰的代码块。通过指定不同的对象,可以细粒度地控制并发访问。
Object lock = new Object();
synchronized (lock) {// 代码块
}
  1. 类对象(Class对象):使用 synchronized 修饰类对象时,该类的所有实例方法和代码块都会受到影响。同一时间只能有一个线程访问该类的任意实例方法或代码块。
public class MyClass {public void instanceMethod() {synchronized (MyClass.class) {// 代码块}}
}

需要注意的是,当多个线程访问共享资源时,使用 synchronized 可以确保线程安全性,避免数据竞争和并发问题。但过度使用 synchronized 可能会导致性能下降,因此在设计并发访问时应根据需求谨慎使用该关键字。

③互斥范围

互斥范围指的是在多线程环境下,保证共享资源访问的互斥性的范围。通过使用锁机制来控制互斥范围,确保同一时间只有一个线程可以访问共享资源,从而避免出现竞态条件和数据不一致的问题。具体来说,互斥范围取决于使用锁的方式和锁的粒度:1. 锁的方式:- 悲观锁:使用关键字 `synchronized` 或 `Lock` 接口的实现类来对关键代码块或方法进行加锁,从而将整个关键代码块或方法作为互斥范围。- 乐观锁:通过无锁算法或基于 CASCompare and Swap)操作的乐观并发控制方法,将互斥范围缩小到共享资源的具体操作。2. 锁的粒度:- 细粒度锁:通过细致划分共享资源,使用更小的锁粒度,使得同时访问不同共享资源的线程之间不会相互阻塞。- 粗粒度锁:使用较大的锁粒度,将多个相关的共享资源放在同一锁范围内,虽然减少了锁的开销,但可能导致线程之间的竞争和阻塞增加。在选择互斥范围时,需要综合考虑以下因素:
- 线程安全性需求:确定哪些共享资源需要保证线程安全,在需要的地方加锁。
- 性能要求:尽量减小互斥范围,避免不必要的锁竞争和阻塞,提升程序的并发性能。
- 锁的粒度:根据具体场景和共享资源关系,选择合适的锁粒度,平衡线程同步和性能开销。需要特别注意的是,锁的粒度过小可能导致频繁的锁竞争和上下文切换,而锁的粒度过大可能限制了并发性能。因此,在设计并发程序时,需要仔细评估并选择适当的互斥范围和锁粒度。

悲观锁(写多读少):

悲观锁是一种并发控制的机制,它基于悲观的假设:在多线程环境下,总是假设会发生并发冲突,并通过加锁来保证共享资源的互斥访问。悲观锁的实现通常基于以下两个主要的机制:1. 基于互斥锁:- 使用关键字 `synchronized``Lock` 接口的实现类,将关键代码块或方法进行加锁。- 当某个线程获得锁后,其他线程需要等待该线程释放锁才能继续执行,从而保证了共享资源的互斥访问。2. 基于数据库锁:- 在关系型数据库中,悲观锁可以通过使用 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE 语句来实现。- SELECT ... FOR UPDATE 语句会对查询结果的行加排他锁,阻塞其他事务的写操作。- SELECT ... LOCK IN SHARE MODE 语句会对查询结果的行加共享锁,阻塞其他事务的写操作,但允许其他事务读取数据。悲观锁的优点在于它保证了数据的一致性和安全性,通过加锁来避免并发冲突。但也存在以下一些注意事项:1. 锁的粒度:悲观锁的粒度通常较大,因为一旦获得了锁,其他线程就需要等待。如果锁的粒度过大,可能会导致线程间的竞争和阻塞增加,影响并发性能。
2. 死锁风险:悲观锁如果使用不当,可能会产生死锁问题。当多个线程相互等待对方释放锁时,可能会导致死锁发生,造成程序无法继续执行。在实际应用中,悲观锁常用于写多读少的场景,例如数据库中的悲观并发控制、文件操作等。它保证了数据的安全性和一致性,但也引入了线程间的竞争和阻塞。开发人员需要合理设计锁的粒度和避免死锁问题,以提高并发性能和避免系统崩溃。

*死锁:

死锁是多线程编程中常见的问题,当两个或多个线程互相持有对方所需的锁资源,并且彼此等待对方释放锁时,就会发生死锁。Java中的死锁问题可以通过以下方式导致:1. 互斥条件:线程同时申请多个锁资源,并且这些锁资源是互斥的(只能由一个线程持有)。
2. 请求和保持条件:线程在持有锁资源的同时继续请求其他锁资源。
3. 不可剥夺条件:已经获得的锁资源不能被其他线程强制性剥夺。
4. 循环等待条件:多个线程形成循环等待锁资源的关系。为了有效地避免死锁问题,可以采取以下几种策略:1. 避免策略:- 分析和设计系统,尽量避免出现死锁发生的情况,避免存在循环等待条件。- 尽量按照固定顺序获取锁资源,避免不同线程获取锁的顺序不一致导致死锁。- 避免长时间持有锁资源,及时释放不再需要的锁。2. 检测与恢复策略:- 可以使用工具或算法来检测死锁的发生,例如死锁检测算法中的银行家算法。- 一旦检测到死锁,可以采取一些措施来恢复系统,如强制释放一部分或所有的锁资源,终止某些线程等。3. 鸵鸟策略:- 对于某些情况下死锁发生的概率较低,或者代价过高的死锁恢复策略,可以选择忽略死锁问题,但要确保死锁不会造成系统崩溃。4. 预防策略:- 在设计和开发过程中,采用良好的编码规范,避免潜在的死锁问题。- 尽量减少锁的使用,采用无锁算法或使用并发容器等替代方案。- 注意锁的粒度控制,尽量保持锁的范围小,减少死锁风险。总之,避免死锁问题需要综合考虑系统设计、并发控制策略和编码规范等方面。通过合理的锁使用、资源申请顺序的规划和及时释放锁资源等方法,可以有效预防和解决死锁问题。

乐观锁(读多写少):

乐观锁是一种并发控制的机制,它通过假设在绝大多数情况下不会出现冲突来提高并发性能。与悲观锁相比,乐观锁不需要加锁,而是在更新共享资源之前进行验证。如果发现其他线程已经修改了共享资源,就会回滚当前操作或重试。乐观锁的实现通常基于以下两个主要的机制:1. 版本号机制:- 在共享资源中引入一个版本号字段,通常是一个整数或时间戳。- 当读取共享资源时,同时将版本号记录下来。- 在更新共享资源时,检查版本号是否被其他线程修改过。如果没有修改,则继续执行更新操作;否则,根据相应策略进行回滚或重试。2. CASCompare and Swap)操作:- 使用原子操作 CAS 来实现乐观锁。- CAS 包含三个操作数:内存地址 V、期望值 A 和新值 B- 当前线程将期望值 A 和内存地址 V 中的实际值进行比较,如果相等,则将内存地址 V 中的值替换为新值 B;否则,不做任何操作。- CAS 是一种无锁算法,可以保证原子性,因此用于实现乐观锁时非常方便。乐观锁的优点在于它避免了线程之间的阻塞和等待,提高了并发性能。不过,乐观锁也有一些注意事项:1. 冲突检测:在更新共享资源之前,需要检测其他线程是否已经修改了该资源。如果发现冲突,可能需要进行回滚或重试操作。
2. 数据一致性:乐观锁只是假设并发冲突很少发生,因此不会对共享资源进行加锁保护。这可能导致数据一致性问题,需要开发人员自行处理。在实际应用中,乐观锁常用于读多写少的场景,例如数据库中的乐观并发控制、缓存更新等。它可以提高并发性能,减少锁竞争,但需要开发人员合理设计并处理冲突和一致性问题。

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

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

相关文章

华为OD机试真题-测试用例执行计划

测试用例执行计划 题目描述: 某个产品当前迭代周期内有N个特性({F1,F2,...,FN})需要进行覆盖测试,每个特性都被评估了对应的优先级,特性使用其ID作为下标进行标识。 设计了M个测试用例({T1,T2,...,TM}),每个用例对应了一个覆盖特…

48、兰州大学、青海师范:专门用于深度CNNs的天阶斗技-ELA Local Attention

本文由兰州大学信息科学与工程学院、青海省物联网重点实验室、青海师范大学于2024年3.2日发表于ArXiv。为了解决现有的注意力模型在有效利用空间信息方面存在的限制和困难,提出了一种高效的局部注意力ELA模型。该方法通过分析坐标注意力的局限性,作者识别…

使用Lerna + Yarn Workspace管理Monorepo项目

1.前言 通常,我们会根据自身业务的实际情况,将通用的组件、逻辑等提取成NPM包,方便以后复用。但这些提取出来的NPM包可能互相之间存在依赖,如果仍然采用 Multirepo 的形式进行管理,则在包的版本管理、依赖管理、调试等…

项目解决方案:多地5G蓄能电站的视频监控联网系统设计方案

目 录 一、前言 二、系统架构设计 1、系统架构设计说明 2、系统拓扑图 三、关键技术 1. 5G支持技术 2. 视频图像处理技术 3. 数据融合与分析技术 四、功能特点 1. 高效可靠 2. 实时监测 3. 远程控制 4. 故障预测 五、应用前景 一、前言 随着能源…

C++泛型实现搜索二叉树

文章目录 二叉搜索树查找插入删除实现应用性能分析 二叉搜索树 二叉搜索树(BST,Binary Search Tree)又称为二叉排序树,空树也算 二叉搜索树有如下性质 若左子树不为空,则左子树上所有节点值小于根节点若右子树不为空…

2575. 找出字符串的可整除数组(Go语言)

https://leetcode.cn/problems/find-the-divisibility-array-of-a-string/ 在看题解之前,我的代码是以下这样: package mainimport ("fmt" )func main() {fmt.Println(divisibilityArray("998244353", 3)) }func divisibilityArray…

供应链管理系统(SCM):得供应链得天下不是空话。

2023-08-26 15:51贝格前端工场 Hi,我是贝格前端工场,优化升级各类管理系统的界面和体验,是我们核心业务之一,欢迎老铁们评论点赞互动,有需求可以私信我们 一、供应链对于企业的重要性 供应链对企业经营的重要性不可…

使用plasmo框架开发浏览器插件,注入contents脚本和给页面添加UI组件

plasmo:GitHub - PlasmoHQ/plasmo: 🧩 The Browser Extension Framework plasmo是一个开发浏览器插件的框架,支持使用react和vue等技术,而且不用手动管理manifest.json文件,框架会根据你在框架中的使用,自…

入门了解huggingface实现ALBERT模型相关任务--Token Classification

目录 AlbertForTokenClassification 主要参数和方法 使用示例 TFAlbertForTokenClassification 参数说明 方法说明 使用示例 FlaxAlbertForTokenClassification 参数说明 __call__ 方法参数 返回值 使用示例 AlbertForTokenClassification AlbertForTokenClassifi…

ChatGPT高效提问——角色提示

ChatGPT高效提问——角色提示 角色提示技巧是一种通过给模型提供具体的角色扮演,指导ChatGPT输出的方法。这个技巧对一个具体的上下文或者听众定制生成的文本很有用。 要使用角色提示技巧,你需要提供明确具体的模型扮演的角色。 例如,如果…

多光谱防伪技术

近年来,随着机器学习的快速崛起,很快出现新的技术更新,刚开始的多层感知机,到现在的大模型,都是大家探讨的兴趣方向,但是最值得大家感兴趣的那就是多光谱防伪技术的出现,很快各大平台都是争先恐后的集中人才开发新的方向和挖掘新的技术。 多光谱技术是什么呢,当然就是…

如何在Windows环境下编译OpenOCD

1. 安装Cygwin Windows环境下编译OpenOCD可以是在MinGW-w64/MSYS或Cygwin下,这里选择Cygwin,下载安装Cygwin。 2. 进入OpenOCD源代码目录 打开Cygwin,进入OpenOCD源代码目录,例如代码放在D:\Temp\OpenOCD\openocd-code下&#…

C++学习笔记:AVL树

AVL树 什么是AVL树?AVL树节点的定义AVL树的插入平衡因子调整旋转调整左旋转右旋转左右双旋右左双旋 AVL树完整代码实现 什么是AVL树? AVL是1962年,两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis 为了解决如果数据有序或接近有序二叉搜索树将退化为单支树,查找…

限制员工上网行为,如何有效管控员工上网行为? 你一定想不到这个方法!

发现员工上班时间刷抖音: 面对这种情况,领导不得火冒三丈??? 对于员工不恰当的上网行为,非常有可能导致工作效率低下、安全风险增加以及企业形象受损。 因此应该采取一些措施来对员工上网行为进行管理。 …

第三节:在Sashulin中自定义组件

上一节讲解了如何建立一个业务消息流,流程是由组件构成的。目前SMS提供了General、Database、MessageQueue、Socket、WebService、Http、Internet等系列常用组件,如果不满足业务需求,可以进行自定义组件开发。 一、组件开发 1、建立一个Jar…

C及C++每日练习(3)

选择题&#xff1a; 1.以下程序的输出结果是&#xff08;&#xff09; #include <stdio.h> main() { char a[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, *p; int i; i 8; p a i; printf("%s\n", p - 3); } A.6 B. 6789 C. 6 D.789 对于本题&#xff0…

吴恩达机器学习-可选实验室:特征工程和多项式回归(Feature Engineering and Polynomial Regression)

文章目录 目标工具特征工程和多项式回归概述多项式特征选择功能备用视图扩展功能复杂的功能 恭喜! 目标 在本实验中&#xff0c;你将:探索特征工程和多项式回归&#xff0c;它们允许您使用线性回归的机制来拟合非常复杂&#xff0c;甚至非常非线性的函数。 工具 您将利用在以…

2023最新pytorch安装教程,简单易懂,面向初学者(Anaconda+GPU)

一、前言 目前是2023.1.27,鉴于本人安装过程中踩得坑&#xff0c;安装之前我先给即将安装pytorch的各位提个醒&#xff0c;有以下几点需要注意 1.判断自己电脑是否有GPU 注意这点很重要&#xff0c;本教程面向有NVIDA显卡的电脑&#xff0c;如果你的电脑没有GPU或者使用AMD显…

.Net Core/.net 6/.Net 8 实现Mqtt服务器

.Net Core/.net 6/.Net 8 实现Mqtt服务端 Mqtt服务端代码IMqttServer 接口业务类&#xff0c;实现 IMqttServer 接口Program.cs 直接上代码 nuget 引用 MQTTnet Mqtt服务端代码 using MQTTnet; using MQTTnet.Protocol; using MQTTnet.Server;namespace Code.Mqtt {/// <sum…

STM32day3

1.思维导图 1.总结任务的调度算法&#xff0c;把实现代码再写一下 /* Definitions for myTask02 */ osThreadId_t myTask02Handle; uint32_t myTask02Buffer[ 64 ]; osStaticThreadDef_t myTask02ControlBlock; const osThreadAttr_t myTask02_attributes {.name "myTa…