数据结构第32节 无锁编程

在Java中,无锁编程是一种在多线程环境下避免使用传统锁机制(如synchronized关键字或ReentrantLock)的技术。这种技术可以提高程序的并发性能,尤其是在高并发场景下,因为它减少了锁的竞争和上下文切换的开销。

数据结构与无锁编程

无锁编程通常涉及使用原子操作、CAS(Compare and Swap)指令以及尾部指针等技术来实现线程安全的数据结构。下面是一些常见的无锁数据结构及其在Java中的实现方式:

1. 原子变量

Java提供了AtomicInteger, AtomicLong, AtomicReference等类,它们支持原子操作,可以在不使用锁的情况下更新变量。

2. 无锁队列

例如,ConcurrentLinkedQueue就是一种无锁队列,它使用了CAS操作来保证线程安全。每次插入或删除操作都会尝试修改队列的头部或尾部引用,如果修改失败(因为有其他线程正在修改),则会重试直到成功。

3. 无锁栈

可以使用AtomicReference来实现一个无锁栈。栈顶元素作为AtomicReference对象的值,入栈和出栈操作都通过CAS操作进行。

4. 无锁列表

类似地,可以使用AtomicReference来维护一个双向链表,其中每个节点包含指向下一个节点的AtomicReference字段,这样可以实现线程安全的插入和删除操作。

5. 无锁哈希表

无锁哈希表通常使用细粒度锁或无锁算法实现。例如,ConcurrentHashMap在JDK 8及更高版本中采用了基于数组+链表/红黑树的结构,并且使用了CAS操作和分割锁来实现线程安全。

实现细节

无锁编程的关键在于使用CAS操作。CAS操作是一个原子操作,它尝试将内存位置的值从旧值更新为新值,只有当该位置的值仍然为旧值时才会成功。如果CAS操作失败,则需要重试,直到成功为止。

例如,在实现无锁队列时,入队操作可能如下所示:

public class LockFreeQueue<T> {private static class Node<T> {T data;AtomicReference<Node<T>> next;public Node(T data) {this.data = data;this.next = new AtomicReference<>(null);}}private final AtomicReference<Node<T>> tail = new AtomicReference<>(new Node<>(null));public void enqueue(T item) {Node<T> newNode = new Node<>(item);Node<T> currentTail = tail.get();while (!currentTail.next.compareAndSet(null, newNode)) {currentTail = tail.get(); // 重试,因为tail可能已经被更新了}tail.compareAndSet(currentTail, newNode); // 更新tail}
}

无锁编程需要对并发编程有深入的理解,包括了解CPU缓存一致性、内存模型、ABA问题等概念。此外,无锁编程虽然可以提高并发性能,但代码往往更复杂,调试也更加困难。因此,在设计时需要权衡其利弊。

无锁编程在Java中可以通过使用java.util.concurrent.atomic包下的类来实现,这些类提供了原子操作的支持,从而可以构建高性能的并发数据结构。下面我将展示如何使用AtomicReference来实现一个简单的无锁栈。

无锁栈实现

我们将创建一个无锁栈LockFreeStack,它将使用AtomicReference来存储栈顶元素的引用。这个栈将提供pushpop操作。

首先定义一个Node类,用于存储栈中的元素和下一个节点的引用:

static class Node<T> {final T data;final AtomicReference<Node<T>> next;Node(T data) {this.data = data;this.next = new AtomicReference<>(null);}
}

然后是LockFreeStack类的实现:

import java.util.concurrent.atomic.AtomicReference;public class LockFreeStack<T> {private final AtomicReference<Node<T>> top = new AtomicReference<>(null);public void push(T value) {Node<T> newNode = new Node<>(value);Node<T> oldTop;do {oldTop = top.get();newNode.next.set(oldTop);} while (!top.compareAndSet(oldTop, newNode));}public T pop() {Node<T> oldTop, newTop;do {oldTop = top.get();if (oldTop == null) return null; // 栈为空newTop = oldTop.next.get();} while (!top.compareAndSet(oldTop, newTop));return oldTop.data;}
}

应用案例

为了验证LockFreeStack的正确性和性能,我们可以创建一个多线程环境,其中多个线程同时向栈中添加和移除元素。这将模拟高并发场景下栈的操作。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;public class LockFreeStackTest {public static void main(String[] args) throws InterruptedException {LockFreeStack<Integer> stack = new LockFreeStack<>();ExecutorService executor = Executors.newFixedThreadPool(10);IntStream.range(0, 10).forEach(i -> {executor.submit(() -> {for (int j = 0; j < 1000; j++) {stack.push(j);if (stack.size() > 100) { // 模拟随机弹出stack.pop();}}});});executor.shutdown();executor.awaitTermination(1, TimeUnit.MINUTES);System.out.println("Final size of the stack: " + stack.size());}public int size() {int size = 0;for (Node<Integer> node = top.get(); node != null; node = node.next.get()) {size++;}return size;}
}

在这个例子中,我们创建了一个固定大小的线程池,并让10个线程分别向栈中添加和移除元素。size()方法用于计算栈中元素的数量,以验证最终状态是否正确。

请注意,实际的生产环境中,你可能需要更复杂的错误处理和测试,以确保无锁数据结构在所有情况下都能正确工作。此外,对于像size()这样的辅助方法,也需要采取适当的措施来确保其线程安全性,可能需要重新设计或者使用其他并发工具来实现。

为了更好地理解上述无锁栈LockFreeStack的代码,我们可以使用表格形式逐步分析pushpop操作的过程。这里假设我们有一个初始为空的栈,并且我们将要执行一系列的pushpop操作。

初始状态

topnext
nullN/A

步骤 1: 第一次 push 操作

假设我们要向栈中push1

push操作前的状态
topnext
nullN/A
push操作过程
  • 创建新节点Node1,数据值为1
  • Node1next设置为top.get(),此时top.get()返回null
  • 使用compareAndSet尝试将top的值从null改为Node1
push操作后的状态
topnext
Node1null

步骤 2: 第二次 push 操作

假设我们要向栈中push2

push操作前的状态
topnext
Node1null
push操作过程
  • 创建新节点Node2,数据值为2
  • Node2next设置为top.get(),此时top.get()返回Node1
  • 使用compareAndSet尝试将top的值从Node1改为Node2
push操作后的状态
topnext
Node2Node1

步骤 3: 第一次 pop 操作

pop操作前的状态
topnext
Node2Node1
pop操作过程
  • 获取top.get(),此时返回Node2
  • top的值从Node2改为Node2.next.get(),即Node1
pop操作后的状态
topnext
Node1null

步骤 4: 第三次 push 操作

假设我们要向栈中push3

push操作前的状态
topnext
Node1null
push操作过程
  • 创建新节点Node3,数据值为3
  • Node3next设置为top.get(),此时top.get()返回Node1
  • 使用compareAndSet尝试将top的值从Node1改为Node3
push操作后的状态
topnext
Node3Node1

通过以上步骤,我们可以看到无锁栈是如何通过AtomicReferencecompareAndSet方法来保证线程安全地进行pushpop操作的。在多线程环境中,即使有多个线程同时尝试修改栈,由于compareAndSet的原子性,也能确保栈的一致性和完整性。

为了提供一个清晰的视图,让我们将上述步骤整合到一个连续的表格中,以展示LockFreeStack在执行一系列pushpop操作时的状态变化。假设我们的操作序列是:push(1), push(2), pop(), push(3)

操作toptop.next描述
初始null-栈初始化为空
push(1)Node1null创建Node1,数据值为1top设为Node1
push(2)Node2Node1创建Node2,数据值为2top设为Node2
pop()Node1null弹出Node2top设为Node1
push(3)Node3Node1创建Node3,数据值为3top设为Node3

这个表格展示了栈在不同操作下的状态转换。每次push操作都会创建一个新的节点,并将其设为新的top,而每次pop操作则会将top的值更新为其next节点,从而实现了栈的基本功能,同时保持了无锁的线程安全特性。

请注意,这个表格简化了AtomicReferencecompareAndSet的内部机制,实际的代码会检查当前top的值是否与预期相同,如果相同则更新top,否则重试,以确保操作的原子性和线程安全性。

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

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

相关文章

【Java】读取超大文件的时候,如何避免出现OOM

读取超大文件的时候&#xff0c;如何避免出现OOM 需求背景如下&#xff1a; 从文件中读取数据并经过业务处理后存储到数据库中&#xff0c;同时避免出现OOM&#xff08;Out of Memory&#xff09; 1、使用分批处理文件数据 将文件数据分批读取&#xff0c;每次只处理一部分数…

深入理解 React 的 useSyncExternalStore Hook

深入理解 React 的 useSyncExternalStore Hook 大家好&#xff0c;今天我们来聊聊 React 18 引入的一个新 Hook&#xff1a;useSyncExternalStore。这个 Hook 主要用于与外部存储同步状态&#xff0c;特别是在需要确保状态一致性的场景下非常有用。本文将深入探讨这个 Hook 的…

GB和GiB的区别

GB&#xff08;Gigabyte&#xff0c;十亿字节&#xff09;和GiB&#xff08;Gibibyte&#xff0c;吉比字节&#xff09;都是数据存储单位&#xff0c;但它们有不同的定义和使用场景。 GB&#xff08;Gigabyte&#xff09; 定义&#xff1a; GB使用十进制系统&#xff0c;1 GB …

Linux系统中通过Wine运行软件实现关机功能

概述 在Linux系统中&#xff0c;我们开发的软件通过Wine进行适配。软件中包含一个需要执行关机操作的功能。然而&#xff0c;发现Windows的关机指令在Linux环境中无效&#xff0c;需要单独设置Linux的关机命令。 一、调用关机脚本文件执行关机 在Linux系统中&#xff0c;可以…

redis存入hash,key=>value和key=>(key=>value)使用Python举例

在 Redis 中&#xff0c;HASH 数据结构&#xff08;也称为 HMAP 或 Hash Map&#xff09;允许你存储键值对集合&#xff0c;其中每个键值对都是字段&#xff08;field&#xff09;和值&#xff08;value&#xff09;的映射。在 Python 中&#xff0c;你可以使用 redis-py 库来与…

Netty Bootstrap/ServerBootstrap

Netty中的Bootstrap和ServerBootstrap是Netty框架中的两个核心引导类&#xff0c;它们分别用于客户端和服务端的启动配置。以下是关于这两个类的详细解析&#xff1a; 一、基本概念 Bootstrap&#xff1a;客户端程序的启动引导类。主要用于配置Netty客户端的各种参数&#xf…

使用phpMyAdmin操作MYSQL(四)

一. 学会phpMyAdmin&#xff1f; phpMyAdminhttp://water.ve-techsz.cn/phpmyadmin/ 虽然我我们可以用命令行操作数据库&#xff0c;但这样难免没有那么直观&#xff0c;方便。所以接下来我们使用phpMyAdmin来操作MySQL&#xff0c;phpMyAdmin是众多MySQL图形化管理工具中使用…

编程从零基础到进阶(更新中)

题目描述 依旧是输入三个整数&#xff0c;要求按照占8个字符的宽度&#xff0c;并且靠左对齐输出 输入格式 一行三个整数&#xff0c;空格分开 输出格式 输出它们按格式输出的效果&#xff0c;占一行 样例输入 123456789 -1 10 样例输出 123456789-1 10 #include "stdio.…

2024年7月20日(星期六)骑行支里山

2024年7月20日 (星期六&#xff09;骑行支里山&#xff0c;早8:00到8:30&#xff0c;大观公园门口集合&#xff0c;9:00准时出发【因迟到者&#xff0c;骑行速度快者&#xff0c;可自行追赶偶遇。】 偶遇地点:大观公园门口集合 &#xff0c;家住东&#xff0c;南&#xff0c;北…

【数据结构】树和二叉树及堆的深入理解

【数据结构】树和二叉树及堆的深入理解 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;数据结构 文章目录 【数据结构】树和二叉树及堆的深入理解前言一.树1.1 树的概念1.2 树的相关概念1.3 树的表示1.4 树的应用 二.二叉树2.1 二叉树概念及…

38 IRF+链路聚合+ACL+NAT组网架构

38 IRF+链路聚合+ACL+NAT组网架构 参考文献 34 IRF的实例-CSDN博客 35 解决单条链路故障问题-华三链路聚合-CSDN博客 36 最经典的ACL控制-CSDN博客 37 公私网转换技术-NAT基础-CSDN博客 32 华三vlan案例+STP-CSDN博客 一 网络架构

智慧煤矿:AI视频智能监管解决方案引领行业新变革

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到各个行业&#xff0c;为传统产业的转型升级提供了强大的动力。在煤矿行业中&#xff0c;安全监管一直是一个重要的议题。为了提高煤矿的安全生产水平&#xff0c;降低事故发生率&#xff0c;智…

ubuntu 虚拟机扩容

在使用vmware创建的ubuntu虚拟机进行linux开发时&#xff0c;安装了docker容器&#xff0c;编译会占用很大的磁盘空间&#xff0c;不想创建新的更大空间的虚拟机linux系统&#xff0c;可以通过gparted图形化工具进行扩容&#xff0c;以下是操作方法 虚拟机设置&#xff0c;扩展…

【C语言】详解结构体(上)

文章目录 前言1. 结构体类型的含义2.结构体的声明2.1 结构体声明的语法2.2 结构体变量的创建和初始化 3.结构体的特殊声明4. 结构体的自引用5.小结 前言 C语言的数据类型分为内置数据类型和自定义的数据类型。所谓的内置的数据类型可以认为是C语言自带的数据类型&#xff08;c…

Java案例遍历集合中的自定义对象

目录 一&#xff1a;案例要求&#xff1a; 二案例分析&#xff1a; ​编辑三&#xff1a;具体代码&#xff1a; 四&#xff1a;运行结果&#xff1a; 一&#xff1a;案例要求&#xff1a; 二案例分析&#xff1a; 三&#xff1a;具体代码&#xff1a; Ⅰ&#xff1a; pack…

Windows系统中MySQL的安装和卸载(详细包含msi和zip下载方式,以及完全卸载方法,易出现问题及解决方案等)

MySQL的安装&#xff1a; 第一种&#xff1a;msi安装&#xff08;交简单&#xff0c;但是不能自定义安装路径&#xff09; 下载地址&#xff1a;https://dev.mysql.com/downloads/installer/ 选择历史版本 选择安装版本&#xff0c;这里我选择的是8.0.37的版本&#xff0c;然…

【HarmonyOS】HarmonyOS NEXT学习日记:二、ArkTs语法

【HarmonyOS】HarmonyOS NEXT学习日记&#xff1a;二、ArkTs语法 众所周知TS是JS的超集,而ArkTs则可以理解为是Ts的超集。他们的基础都基于JS&#xff0c;所以学习之前最好就JS基础。我的学习重点也是放在ArkTs和JS的不同点上。 文章主要跟着官方文档学习&#xff0c;跳过了一…

框架设计MVC

重点&#xff1a; 1.用户通过界面操作&#xff0c;传输到control&#xff0c;control可以直接去处理View&#xff0c;或者通过模型处理业务逻辑&#xff0c;然后将数据传输给view。 2.control包含了model和view成员。 链接&#xff1a; MVC框架详解_mvc架构-CSDN博客 MVC架…

Spring Boot 学习总结(34)—— spring-boot-starter-xxx 和 xxx-spring-boot-starter 区别?

一、Spring Starter 简介 Spring Starter 是 Spring Boot 提供的一种便捷方式,帮助开发者快速集成和配置 Spring 应用中所需的依赖。每个 Starter 都是一个预配置的依赖集,可以自动配置应用的一部分或特定功能。这些 Starter 旨在消除手动编写大量样板代码和配置的需求。 1…

小程序中用于跳转页面的5个api是什么和区别

在微信小程序中&#xff0c;用于页面跳转的API主要有以下几个&#xff0c;但通常不需要5个那么多&#xff0c;因为它们的功能各有侧重&#xff0c;用于不同的跳转场景。以下是这些API及其详细代码和区别&#xff1a; wx.navigateTo(OBJECT) 用于保留当前页面&#xff0c;跳转到…