Java并发编程实战读书笔记(二)

对象的组合

在设计线程安全的类时,确保数据的一致性和防止数据竞争是至关重要的。这通常涉及三个基本要素:确定构成对象状态的所有变量,明确约束这些状态变量的不变性条件,以及建立管理对象状态并发访问的策略。

要确定构成对象状态的所有变量相对简单,但需注意状态应封装在对象内,避免外部直接访问导致数据不一致的风险。例如,一个银行账户类可能将其余额作为其状态变量。

不变性条件则较为复杂,它定义了哪些状态转换是允许的。例如,银行账户的余额不能为负数。在没有充分了解对象的不变形条件与后验条件下,很难保证线程安全性。因此,需要借助原子性和封装性来达成这一点。

并发访问管理策略的选择多样,包括使用synchronized关键字、显式锁如ReentrantLock、原子类如AtomicLong或并发集合如ConcurrentHashMap等。每种策略都有其适用场景,选择哪一种取决于特定的需求和性能考量。

ConcurrentHashMap为例,它通过分段锁机制实现了高并发性和伸缩性,允许多个读取线程和一定量的写入线程并发修改Map,从而提高多线程环境下的性能。相比之下,HashTable则是通过在每个方法上使用synchronized关键字实现同步,但这导致了较低的并发性能。

在某些情况下,即便对象本身是线程安全的,如果将其发布出去也可能破坏封闭性。封闭类的状态使得分析类的线程安全性时无需检查整个程序,从而更易于构造线程安全的类。

数据封装在对象内部可以将数据的访问限制在对象的方法上,这样更容易确保线程在访问数据时总能持有正确的锁。例如,Vector的add方法通过synchronized关键字同步,保证了线程安全,但效率较低。通常建议使用Collections.synchronizedList(arrayList)将线程不安全的ArrayList转换为线程安全的List。

实例封闭

  1. 作用域限制与封闭实例
    • 对象可以封闭在某个作用域内,例如作为类的私有成员或局部变量。封闭在某个作用域内的对象更容易监控和分析,因为能够访问该对象的代码路径是已知的。
    • 例如,线程Local中的变量仅在当前线程中可见和可访问,这极大地减少了多线程之间的数据竞争。每个线程都可以独立地访问自己的副本而不会影响其他线程,从而确保了线程安全性。
  2. 私有锁与内置锁
    • 使用私有的锁对象而不是对象的内置锁或其他公有访问的锁,可以避免客户代码错误地参与到同步策略中。私有锁可以被封装在类内部,仅通过特定的方法暴露必要的同步行为。
    • 例如,使用ReentrantLock作为私有锁可以实现更细粒度的同步控制。在并发编程中,这种私有锁的使用可以有效地保护共享资源,并且更加灵活地设计同步策略,从而提升性能并减少死锁的风险。
  3. 线程局部变量与封闭实例
    • 在某些情况下,可以将对象封闭在线程内,即从一个方法传递到另一个方法中,而不是在多个线程之间共享。这样,对象仅在单个线程内使用,避免了并发访问带来的问题。
    • 例如,在线程中创建的局部对象,只在该线程的生命周期内使用,不会与其他线程共享。这种方法在高并发环境下尤其有用,因为它大大减少了线程间共享数据的可能性。
  4. 公有方法与同步策略
    • 通过提供公有方法来访问和修改封闭对象的状态,可以在这些方法中实现适当的同步策略。例如,可以通过加锁机制(如synchronized关键字或显式锁)来确保每次只有一个线程能够访问对象的某个状态。
    • 这种方法结合了封装和同步的优点,使得对象的状态在多线程环境中保持一致。同时,由于对象的封闭性,分析其线程安全性时只需检查有限的代码路径,无需考虑整个程序的复杂性。
  5. 并发容器与同步包装器
    • Java提供了多种并发容器,如ConcurrentHashMap和CopyOnWriteArrayList等,它们通过精细设计的锁机制和数据结构优化来提高并发性能。
    • 使用Collections.synchronizedList等方法可以将非线程安全的容器转变为线程安全的容器,通过封装来实现同步。这些同步包装器持有对底层容器的唯一引用,并通过同步方法保护所有对底层容器的访问,从而实现线程安全。
  6. 封闭机制与加锁策略
    • 封闭机制与合适的加锁策略结合起来,可以确保以线程安全的方式来使用非线程安全的对象。例如,在封闭的作用域内使用局部锁可以进一步限制对象的访问范围,从而降低数据竞争的风险。
    • 封闭机制使得不同的状态变量可以由不同的锁来保护,这样的灵活性有助于设计高效且线程安全的类。例如,分离锁(Separate Locks)模式允许不同的操作持有不同的锁,从而增加并发性。
  7. 发布控制与逸出检查
    • 封闭对象不应发布到其封闭作用域之外的环境中。对象的发布可能会导致封闭性的破坏,使得对象被外部代码以不受控的方式访问和修改。
    • 例如,从方法返回一个内部的私有对象的引用,或者在公共方法中暴露内部状态的详细信息,都可能破坏封闭性和线程安全性。因此,应严格控制对象的发布,确保不会逸出其封闭的作用域。

基础构建模块

同步容器类

同步容器类包括Vector和Hashtable,它们提供了线程安全的访问方式。然而,在使用这些容器类时,需要注意以下几点:

  1. 对于迭代器,如果在迭代过程中容器被其他线程修改,迭代器会抛出ConcurrentModificationException异常。这是因为迭代器在检查容器是否被修改时没有进行同步,可能会导致失效的计数值。这种设计是为了降低并发修改操作对程序性能的影响。

  2. 对于复合操作(如addAll、removeAll等),即使容器本身是同步的,这些操作也可能不是原子性的。这意味着在执行这些操作时,可能会有其他线程同时修改容器,导致不可预测的结果。为了避免这种情况,可以使用锁或其他同步机制来确保复合操作的原子性。

  3. 对于可变容器,如ArrayList和HashMap,虽然它们本身不是同步的,但可以通过使用Collections.synchronizedList或Collections.synchronizedMap方法将它们包装成同步容器。这样,在使用这些容器时,需要手动进行同步以确保线程安全。

并发容器

并发容器是Java 5.0引入的新型容器,它们为多线程环境提供了更好的性能和并发性。与同步容器不同,并发容器使用更细粒度的锁或无锁算法来提高并发访问的性能。下面详细介绍几种主要的并发容器:

ConcurrentHashMap

ConcurrentHashMap是Java提供的一个线程安全的哈希表,它通过分段锁(Lock Striping)技术提高了并发性能。在ConcurrentHashMap中,整个哈希表被分成多个段,每个段都有自己的锁。这样,不同段的更新操作可以并行进行,从而提升了整个哈希表的并发能力。

CopyOnWriteArrayList

CopyOnWriteArrayList是一个线程安全的列表,它采用了“写入时复制”(Copy-On-Write)策略。这意味着每次对列表进行修改操作(如添加、删除元素)时,都会创建并重新发布一个新的列表副本,而不是在原有列表上进行修改。这种策略适合读操作远多于写操作的场景,因为读操作通常可以在不加锁的情况下进行。

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个基于链接节点的、线程安全的队列。它使用了无锁算法来实现并发控制,从而提高了并发性能。

BlockingQueue

BlockingQueue是一个扩展了Queue接口的阻塞队列,它支持可阻塞的插入和获取等操作。如果队列为空,获取元素的操作将一直阻塞,直到队列中有元素可用;如果队列已满(对于有界队列),插入元素的操作将一直阻塞,直到队列中有空间可用。

ConcurrentSkipListMap 和 ConcurrentSkipListSet

ConcurrentSkipListMap和ConcurrentSkipListSet分别是基于跳表实现的线程安全的映射和集合。它们提供了类似TreeMap和TreeSet的排序功能,但在并发性能上进行了优化。

Deque

Deque(双端队列)是一个可以在两端进行插入和删除操作的线性数据结构。在Java中,Deque接口继承自Queue接口,因此它具有Queue的所有功能,同时还提供了额外的方法来支持从两端的操作。

ArrayDeque是Deque接口的一个实现,它使用数组作为底层数据结构。由于数组支持随机访问,因此ArrayDeque可以在常数时间内完成头部和尾部的插入和删除操作。

LinkedBlockingDeque也是Deque接口的一个实现,但它是线程安全的。它使用链表作为底层数据结构,并通过锁和其他同步机制来实现线程安全。

以下是一些使用Deque和ArrayDeque的示例代码:

import java.util.ArrayDeque;
import java.util.Deque;public class DequeExample {public static void main(String[] args) {// 创建一个ArrayDeque实例Deque<Integer> deque = new ArrayDeque<>();// 在队列头部插入元素deque.addFirst(1);deque.addFirst(2);deque.addFirst(3);// 在队列尾部插入元素deque.addLast(4);deque.addLast(5);// 打印队列内容System.out.println("Deque: " + deque);// 移除并返回队列头部的元素int firstElement = deque.removeFirst();System.out.println("Removed first element: " + firstElement);// 移除并返回队列尾部的元素int lastElement = deque.removeLast();System.out.println("Removed last element: " + lastElement);// 打印队列内容System.out.println("Deque after removals: " + deque);}
}

这段代码首先创建了一个ArrayDeque实例,然后在队列头部和尾部分别插入了一些元素。接着,它移除并打印了队列头部和尾部的元素。最后,它打印了移除元素后的队列内容。

CountDownLatch

闭锁(Latch)是一种同步工具类,用于控制线程的执行顺序。它允许一个或多个线程等待一组事件发生后再继续执行。闭锁的状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。当事件发生时,计数器递减,而await方法则等待计数器达到零,这表示所有需要等待的事件都已经发生。如果计数器的值非零,那么await会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时。

CountDownLatch是闭锁的一种实现,它可以使一个或多个线程等待一组事件发生。例如,可以使用CountDownLatch来确保某个计算在其需要的所有资源都被初始化之后才继续执行;确保某个服务在其依赖的所有其他服务都已经启动之后才启动;等待直到某个操作的所有参与者都就绪再继续执行。

Semaphore

计数信号量(Counting Semaphore)是一种同步工具类,用于控制同时访问某个特定资源的操作数量或执行某个指定操作的数量。它通过管理一组虚拟许可来实现同步,许可的初始数量可通过构造函数指定。

在使用计数信号量时,线程可以首先尝试获得许可,如果还有剩余的许可,线程可以继续执行,并在使用完资源后释放许可。如果没有可用的许可,acquire方法将阻塞直到有可用许可,或者等待被中断或超时。release方法则用于将许可返回给信号量。

二值信号量是计数信号量的一种特殊形式,其初始值为1,可以用做互斥体(mutex),并具备不可重入的加锁语义。当一个线程拥有这个唯一的许可时,它就拥有了互斥锁。

计数信号量还可以用于实现资源池,如数据库连接池。在这种情况下,可以将Semaphore的计数值初始化为池的大小,并在从池中获取资源之前首先调用acquire方法获取许可。当资源使用完毕后,调用release方法释放许可。这样,acquire方法会一直阻塞直到资源池不为空。

另一种简单的实现阻塞对象池的方法是使用BlockingQueue来保存池中的资源。

CyclicBarrier

栅栏(Barrier)是一种特殊的同步工具,它能使一组线程在继续执行前等待直到所有线程都到达某个特定点。栅栏的关键特性是所有线程都必须同时到达栅栏位置才能继续执行,这与闭锁不同,闭锁用于等待某个事件发生。

CyclicBarrier是栅栏的一个实现,它允许一定数量的线程反复地在某个点汇集。这对于并行迭代算法特别有用,这种算法通常将一个问题分解成一系列独立的子问题。当线程到达栅栏位置时,它会调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。一旦所有线程都到达,栅栏将打开,所有线程将被释放,并且栅栏将被重置以便再次使用。

如果对await的调用超时或被中断,栅栏将被认为是“打破”的,此时所有阻塞的await调用都将终止并抛出BrokenBarrierException。如果成功通过栅栏,await将为每个线程返回一个唯一的到达索引号。这个索引可以用来选举一个领导线程,在下一次迭代中执行一些特殊任务。

CyclicBarrier还允许你传递一个栅栏操作给构造函数,这是一个Runnable,当成功通过栅栏时,它将在一个子任务线程中执行,但这必须在释放阻塞线程之前完成。

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

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

相关文章

定时器+外部中断实现NEC红外线协议解码

一、前言 1.1 功能介绍 随着科技的进步和人们生活水平的提高&#xff0c;红外遥控器已经成为了日常生活中不可或缺的电子设备之一&#xff0c;广泛应用于电视、空调、音响等多种家电产品中。 传统的红外遥控器通常只能实现预设的有限功能&#xff0c;无法满足用户对设备更加智…

创建vue2/vue3项目

目录 创建一个Vue2项目创建一个Vue3项目 创建一个Vue2项目 ## 安装Vue-Cli &#xff1a; npm install -g vue/cli // Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)vue --version // 检测版本是否正确## 创建一个项目&#xff1a; vue create hello-world // hel…

TCP客户端connect断线重连

文章目录 TCP客户端connect断线重连1、为什么要断线重连2、实现代码 TCP客户端connect断线重连 1、为什么要断线重连 客户端会面临服务器崩溃的情况&#xff0c;我们可以试着写一个客户端重连的代码&#xff0c;模拟并理解一些客户端行为&#xff0c;比如游戏客户端等. 考虑到…

实战篇(十二):如何使用 Processing 创建一个多功能的简易吃豆人游戏

如何使用 Processing 创建一个多功能的简易吃豆人游戏 文章目录 如何使用 Processing 创建一个多功能的==简易==吃豆人游戏引言准备工作第一步:设置基本框架第二步:创建 Pacman 类第三步:创建 Obstacle 类第四步:添加分数系统第五步:运行游戏完整代码结论参考资料引言 吃…

c++中的哈希查找(Hash Search)和B树查找(B-Tree Search)

前言 hello大家好啊&#xff0c;我是文宇&#xff0c;不是文字&#xff0c;是文宇哦&#xff0c;这期也是关于查找算法的。 哈希查找&#xff08;Hash Search&#xff09; 哈希查找&#xff08;Hash Search&#xff09;是一种基于哈希表的查找算法&#xff0c;它可以在常数时…

STL常用算法——常用查找算法

自定义类型都要用仿函数判断 1.find() class Person { public:Person(string name,int age){this->m_Name name;this->m_Age age;}bool operator(const Person &p)//重载operator{if (this->m_Name p.m_Name && this->m_Age p.m_Age){return true;…

【云原生】NameSpace名称空间详解

名称空间 文章目录 名称空间一、名字空间二、何时使用多个名称空间三、初始名称空间3.1、default3.2、kube-node-lease3.3、kube-public3.4、kube-system 四、通过名称空间共享集群4.1、查看名称空间4.2、获取名称空间详细信息4.3、名称空间的两种状态4.4、创建名称空间4.5、删…

NVIDIA 全面转向开源 GPU 内核模块

NVIDIA 全面转向开源 GPU 内核模块 文章目录 NVIDIA 全面转向开源 GPU 内核模块支持的 GPU安装程序更改使用带有 CUDA 元包的包管理器 使用运行文件使用安装帮助脚本包管理器详细信息dnf&#xff1a;Red Hat Enterprise Linux、Fedora、Kylin、Amazon Linux 或 Rocky Linuxzypp…

网络安全等级保护:什么是网络安全等级保护?(非常详细)零基础入门到精通,收藏这一篇就够了

关键词&#xff1a; 网络安全等级保护 等级保护 网络 信息系统 旧话重提&#xff0c;一直以来&#xff0c;我们不断强调“等级保护”制度是我国的网络安全领域的基本制度、基本策略和基本方法&#xff0c;是促进信息化健康发展&#xff0c;维护国家安全、社会秩序和公共利益的…

数字图像处理中的常用特殊矩阵及MATLAB应用

一、前言 Matlab的名称来源于“矩阵实验室&#xff08;Matrix Laboratory&#xff09;”&#xff0c;其对矩阵的操作具有先天性的优势&#xff08;特别是相对于C语言的数组来说&#xff09;。在数字图像处理中&#xff0c;为了提高编程效率&#xff0c;我们可以使用多种方式来创…

Mysql数据库和Sql语句

数据库管理&#xff1a; sql语句&#xff1a;数据库用来增删改查的语句&#xff08;重要&#xff09; 备份&#xff1a;数据库的数据进行备份 主从复制、读写分离、高可用&#xff08;重要&#xff09; Mysql数据库和Sql语句 一、Mysql数据库 1、数据库&#xff1a;组织、…

Java基础(四) 内部类详解

Java 内部类详解 一. 内部类概述 内部类是嵌套在类内部进行定义的类&#xff0c;其外部的类则被称为外部类&#xff1b;按照内部类的定义位置&#xff0c;内部类可进一步划分为成员内部类、静态内部类、局部内部类和匿名内部类四种类型。内部类的出现实际上是进一步丰富了类的…

使用Python实现深度学习模型:用户行为预测与个性化服务

介绍 在这篇教程中,我们将构建一个深度学习模型,用于用户行为预测和个性化服务。我们将使用TensorFlow和Keras库来实现这一目标。通过这个教程,你将学会如何处理数据、构建和训练模型,并将模型应用于实际的用户行为预测和个性化服务任务。 项目结构 首先,让我们定义项目…

Modbus转BACnet/IP网关的技术实现与应用

引言 随着智能建筑和工业自动化的快速发展&#xff0c;不同通信协议之间的数据交换也变得日益重要。Modbus和BACnet/IP是两种广泛应用于自动化领域的通信协议&#xff0c;Modbus以其简单性和灵活性被广泛用于工业自动化&#xff0c;而BACnet/IP则在楼宇自动化系统中占据主导地…

Android APP 音视频(03)CameraX预览与MediaCodec编码

说明&#xff1a; 此CameraX预览和编码实操主要针对Android12.0系统。通过CameraX预览获取yuv格式数据&#xff0c;将yuv格式数据通过mediacodec编码输出H264码流&#xff08;使用ffmpeg播放&#xff09;&#xff0c;存储到sd卡上。 1 CameraX 和 MediaCodec简介 1.1 CameraX…

Redission中的Lua脚本写法、理解

对于Redission看门狗机制中的为了保证原子性的Lua脚本的写法规则是什么样的呢 &#xff1f; 对于源码中的Lua脚本又是什么意思&#xff1f; 我们一起来看一下 首先&#xff0c;我们先基本的熟悉一下lua脚本的逻辑 在Lua脚本中&#xff0c;if (…) then … end 语句的执行过程…

“微软蓝屏”事件,给IT行业带来的宝贵经验和教训

“微软蓝屏”事件是指2024年7月19日发生的一次全球性技术故障&#xff0c;主要涉及微软视窗&#xff08;Windows&#xff09;操作系统及其相关应用和服务。 以下是对该事件的详细解析&#xff1a; 一、事件概述 发生时间&#xff1a;2024年7月19日事件影响&#xff1a;全球多个…

【科学文献计量】中国知网(CNKI) 文献素材库生成软件详细使用说明

CNKI 文献素材库生成软件制作 1 背景2 使用步骤2.1 文献检索2.2 文献导出2.3 软件生成1 背景 在进行中文文献的综述时,往往是要借助中国知网(CNKI)文献检索平台,写作插入文献时会用Endnote软件进行辅助。因此就有需求:对于CNKI检索的结果直接导出到本地,第一是方便快速阅…

com.alibaba.fastjson.JSONObject类介绍、应用场景和示例代码

概述 功能特点&#xff1a; 解析 JSON 字符串为 Java 对象。将 Java 对象序列化为 JSON 字符串。支持链式操作&#xff0c;便于对 JSON 数据进行增删改查。 应用场景&#xff1a; 与前端进行 JSON 数据的交互&#xff0c;如 RESTful API 的数据传输。处理第三方接口返回的 …

基于STM32的农业大棚温湿度采集控制系统的设计

目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 &#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是智能教室。 设备的详细功能见网盘中的文章《8、基…