并发变成实战-原子变量与非阻塞同步机制
文章目录
- 1.锁的劣势
- 2.硬件对并发的支持
- 2.1 比较并交换
- 2.2 非阻塞的计数器
- 3.原子变量类
- 3.1 原子变量是一种“更好的volatile”
- 3.2 性能比较:锁与原子变量
- 4.非阻塞算法
- 4.1 非阻塞的栈
- 4.2 非阻塞的链表
- 4.3 ABA问题
非阻塞算法设计和实现上要复杂的多,但在可伸缩性和活跃性上拥有巨大的优势。
原子变量提供了与volatile变量相同的语义,此外还支持原子的更新操作,从而更适用于实现计数器、序列发生器和统计数据收集等。
1.锁的劣势
多个线程请求锁时,一些线程将被挂起并且在稍后恢复运行。当线程恢复执行时,必须等待其他线程执行完他们的时间片后才能被调度执行。挂起和恢复线程等过程存在很大的开销,并且通常存在较长时间的中断。
volatile更轻量级的同步机制,不会发生上下文切换或线程调度等操作。局限:虽然提供了可见性,但不能用于构建原子的复合操作。因此当一个变量依赖其它变量或新值依赖于旧值时,就不能用volatile(例如,++i 包含了3个独立的操作–获取变量当前值,加1,写入新值,整个过程必须是原子的)。
锁定时,当一个线程等待锁时,它不能做其它的事情。如果一个线程在持有锁时被延迟执行(如缺页错误、调度延迟等),所有需要这个锁的其它线程都无法执行。如果持有锁的线程永久阻塞,那么程序将永远无法继续执行。
2.硬件对并发的支持
独占锁是一种悲观锁–假设最坏的情况
对于细粒度的操作,有一种乐观的方法,可以不发生干扰的情况下完成更新操作。这种方法需要借助冲突检查机制来判断在更新过程中是否存在来自其他线程的干扰,如果存在,这个操作将失败,并且可以重试(CAS等)。
2.1 比较并交换
CAS包含了3个操作数–需要读写的内存位置V、进行比较的值A和拟写入的新值B。当且仅当V的值等于A时,CAS才会用原子方式用新值B来更新V的值,否则不会执行任何操作。无论位置V的值是否等于A,都将返回V原有的值。
CAS含义:我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少。
CAS是一项乐观的技术。如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。
/*** 模拟CAS操作*/
public class SimulatedCAS {private int value;public synchronized int get() {return value;}public synchronized int compareAndSwap (int expectedValue,int newValue) {int oldValue = value;if (oldValue == expectedValue) {value = newValue;}return oldValue;}public synchronized boolean compareAndSet(int expectedValue,int newValue) {return (expectedValue == compareAndSwap(expectedValue,newValue));}
}
多个线程尝试更新同一个变量时,只有一个线程能更新变量的值,而其他线程都将失败。失败的线程不会被挂起,而是会再次尝试。由于线程竞争失败后不会阻塞,因此他可以决定是否重新尝试,或者执行一些恢复操作,或者不执行任何操作。
2.2 非阻塞的计数器
使用CAS实现一个简单的非阻塞计数器
public class CasCounter {private SimulatedCAS value;public int getValue() {return value.get();}public int increment() {int v;do {v = value.get();} while (v != value.compareAndSwap(v,v + 1)) {return v + 1;}}
}
通常,反复的重试是一种合理的策略,但一些竞争激烈的情况下,更好的方式是在重试之前首先等待一段时间或者回退,从而避免造成活锁问题。
CasCounter 不会阻塞,如果其他线程同时更新计数器,那么会多次执行重试操作。(如果仅需要一个计数器或序列生成器,那么可以直接使用AtomicInteger或AtomicLong,他们能提供原子的递增方法)
竞争程度不高时,基于CAS的计数器在性能上远胜于基于锁的计数器。
3.原子变量类
原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger表示一个int类型的值,并提供了get和set方法。发生竞争时有很好的可伸缩性。
共12个原子变量类,分为4组:标量类,更新器类,数组类以及复合变量类。
最常用的原子变量就是标量类:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。他们都支持CAS。
原子数组类(只支持Integer、Long、Reference版本)中的元素可以实现原子更新。原子数组类为数组的元素提供了volatile类型的访问语义,这是普通数组不具备的特性–volatile类型的数组仅在数组引用上具有volatile,在其元素上没有。
原子的标量类扩展了Number类,但并没有扩展一些基本类型的包装类,如Integer或Long,因为基本类型的包装类是不可修改的,而原子变量类是可修改的。
3.1 原子变量是一种“更好的volatile”
public class CasNumberRange {private static class IntPair {//不变形条件: lower <= upperfinal int lower;final int upper;private IntPair(int lower, int upper) {this.lower = lower;this.upper = upper;}}//使用AtomicReference和IntPair来保存状态private final AtomicReference<IntPair> values = new AtomicReference<>(new IntPair(0, 0));public int getLower() {return values.get().lower;}public int getUpper() {return values.get().upper;}public void setLower(int i) {while (true) {IntPair oldv = values.get();if (i > oldv.upper) {throw new IllegalArgumentException("Can not set lower to " + i + " > upper");}IntPair newv = new IntPair(i, oldv.upper);//CAS 避免竞态if (values.compareAndSet(oldv, newv)) {return;}}}
}
3.2 性能比较:锁与原子变量
低竞争情况下,原子变量的性能高于锁,高竞争情况下,锁性能高于原子变量
但实际情况中,原子变量在可伸缩性上要高于锁,因为在应对常见的竞争程度时,原子变量的效率会更高。
4.非阻塞算法
4.1 非阻塞的栈
非阻塞算法通常比基于锁的算法更复杂。算法关键在于,找出如何将原子修改的范围缩小到单个变量上,同时还要维护数据一致性。
栈时最简单的链式数据结构, 每个元素只指向一个元素
public class ConcurrentStack<E> {AtomicReference<Node<E>> top = new AtomicReference<>();public void push(E item) {Node<E> newHead = new Node<>(item);Node<E> oldHead;do {oldHead = top.get();//新节点的next指向当前栈顶newHead.next = oldHead;//使用CAS把新节点放到栈顶//如果开始插入节点前,栈顶没有发生变化,CAS就会成功更新栈顶//如果栈顶发生变化(被其他线程修改),CAS会失败,并根据新的栈状态来更新节点} while (top.compareAndSet(oldHead,newHead));}private static class Node<E> {public final E item;public Node<E> next;public Node(E item) {this.item = item;}}
}
非阻塞算法特性:某工作的完成具有不确定性,必须重新执行。
CAS既能提供原子性,又能提供可见性,所以非阻塞算法是线程安全的。
4.2 非阻塞的链表
链表需要单独维护头指针和尾指针来快速访问。有两个指针指向位于尾部的节点:当前最后一个元素的next指针,以及尾节点。当插入一个元素时,这两个指针都需要采用原子操作来更新。
技巧
- 即使在一个包含多个步骤的更新操作中,也要确保数据结构总是处于一致的状态。B线程到达时,如果A正在执行更新操作,B不能立即开始自己的更新操作。然后B可以等待(通过反复检查队列的状态)并直到A完成
- 如果B到达时发现A正在修改数据结构,那么在数据结构中应该有足够多的信息,使得B能完成A的更新操作。如果B帮助A完成了更新操作,那么B可以执行自己的操作,而不用等A操作完成。当A恢复后视图完成其操作时,会发现B已经替他完成了。
空队列通常包含一个“哨兵节点(Sentinel)”或者“哑节点(Dummy)”,并且头结点和尾结点在初始化时都指向该哨兵节点。尾节点通常要么指向哨兵节点(如果队列为空),即队列的最后一个元素,要么(当有操作正在执行更新操作时)指向倒数第二个元素。
public class LinkedQueue<E> {private static class Node<E> {final E item;final AtomicReference<Node<E>> next;private Node(E item, AtomicReference<Node<E>> next) {this.item = item;this.next = next;}}private final Node<E> dummy = new Node<>(null,null);private final AtomicReference<Node<E>> head = new AtomicReference<>(dummy);private final AtomicReference<Node<E>> tail = new AtomicReference<>(dummy);//插入元素需要更新两个指针public boolean put(E item) {Node<E> newNode = new Node<>(item, null);while (true) {Node<E> curTail = tail.get();Node<E> tailNext = curTail.next.get();if (curTail == tail.get()) {if (tailNext != null) {//队列处于中间状态,推进尾结点tail.compareAndSet(curTail,tailNext);} else {//处于稳定状态,尝试插入新节点if (curTail.next.compareAndSet(null,newNode)) {//插入操作成功,尝试推进尾结点tail.compareAndSet(curTail,newNode);return true;}}}}}
}
插入元素需要更新两个指针。首先更新当前最后一个元素的next指针,将新节点链接到队尾,然后更新尾结点,将其指向这个新的元素。
当队列处于稳定状态,尾结点的next域降为空,如果队列处于中间状态,那么tail.next将为非空。因此任何线程都能通过检查tail.next来获取队列当前的状态。
4.3 ABA问题
CAS判断值相等的间隙,值是否发生过改变
解决方法:改变引用值的时候,增加一个版本号。每次改变的操作都更新版本号。
相关文章:
并发变成实战-原子变量与非阻塞同步机制
文章目录1.锁的劣势2.硬件对并发的支持2.1 比较并交换2.2 非阻塞的计数器3.原子变量类3.1 原子变量是一种“更好的volatile”3.2 性能比较:锁与原子变量4.非阻塞算法4.1 非阻塞的栈4.2 非阻塞的链表4.3 ABA问题非阻塞算法设计和实现上要复杂的多,但在可伸…...
sql数据库常用操作指令
一、操作库-- 创建库create database db1;-- 创建库是否存在,不存在则创建create database if not exists db1;-- 查看所有数据库show databases;-- 查看某个数据库的定义信息 show create database db1; -- 修改数据库字符信息alter database db1 character set ut…...
4-1 定时任务的示例10个
文章目录前言基本命令与格式示例前言 Linux crontab 是用来定期执行程序的命令。当安装完成操作系统之后,默认都已经安装,并启动此任务调度命令。 crond 命令每分钟会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。 基…...
外贸建站多少钱才能达到预期效果?
外贸建站多少钱才能达到预期效果?这是每个外贸企业都会问的问题。作为一个做外贸建站多年的人,我有一些个人的操盘感想。 首先,我认为外贸建站的投资是非常必要的。 因为在现代社会,网站已经成为外贸企业开展业务的必要工具之一…...
【Java学习笔记】5.Java 基本数据类型
Java 基本数据类型 变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。 因此,通过定义不同类型的变量…...
InnoDB 死锁和问题排查
文章目录死锁(dead lock)示例 1问题排查查看连接的线程查看相关的表查看最近一次的死锁信息查看服务器的锁信息查看正在使用的表如何尽可能地避免死锁死锁(dead lock) 两个及以上的事务各自持有对方需要的锁,导致双方…...
tensorflow07——使用tf.keras搭建神经网络(Sequential顺序神经网络)——六步法——鸢尾花数据集分类
使用tf.keras搭建顺序神经网络 六步法——鸢尾花数据集分类 01 导入相关包 02 导入数据集,打乱顺序 03 建立Sequential模型 04 编译——确定优化器,损失函数,评测指标(用哪一种准确率) 05 训练模型——把各项参入填入…...
关于Java连接Hive,Spark等服务的Kerberos工具类封装
关于Java连接Hive,Spark等服务的Kerberos工具类封装 idea连接服务器的hive等相关服务的kerberos认证注意事项 idea 本地配置,连接服务器;进行kerberos认证,连接hive、HDFS、Spark等服务注意事项: 本地idea连接Hadoo…...
大数据框架之Hadoop:MapReduce(五)Yarn资源调度器
Apache YARN (Yet Another Resource Negotiator) 是 hadoop 2.0 引入的集群资源管理系统。用户可以将各种服务框架部署在 YARN 上,由 YARN 进行统一地管理和资源分配。 简言之,Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源&…...
uniapp实现地图点聚合功能
前言 在工作中接到的一个任务,在app端实现如下功能: 地图点聚合地图页面支持tab切换(设备、劳务、人员)支持人员搜索显示分布 但是uniapp原有的map标签不支持点聚合功能(最新的版本支持了点聚合功能)&am…...
经典分类模型回顾2—GoogleNet实现图像分类(matlab版)
GoogleNet是深度学习领域的一种经典的卷积神经网络,其在ImageNet图像分类任务上的表现十分优秀。下面是使用Matlab实现GoogleNet的图像分类示例。 1. 数据准备 在开始之前,需要准备一些图像数据用来训练和测试模型,可以从ImageNet等数据集中…...
Java经典面试题——谈谈 final、finally、finalize 有什么不同?
典型回答 final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展, final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。 finally 则是 Java 保…...
C#的Version类型值与SQL Server中二进制binary类型转换
使用C#语言编写的应用程序可以通过.NET Framework框架提供的Version类来控制每次发布的版本号,以便更好控制每次版本更新迭代。 版本号由两到四个组件组成:主要、次要、内部版本和修订。 版本号的格式如下所示, 可选组件显示在方括号 ([ 和…...
软测入门(五)接口测试Postman
Postman 一款Http接口收工测试工具。如果做自动化测试会使用jemter做。 安装 去官网下载即可。 https://www.postman.com/downloads/?utm_sourcepostman-home 功能介绍 页面上的单词基本上都能了解,不多介绍。 转代码&注释 可将接口的访问转为其他语言的…...
UWB通道选择、信号阻挡和反射对UWB定位范围和定位精度的影响
(一)介绍检查NLOS操作时需要考虑三个方面:(1)由于整体信号衰减,通信范围减小。(2)由于直接路径信号的衰减,导致直接路径检测范围的减小。(3)由于阻…...
linux基本功之列之wget命令实战
文章目录前言一. wget命令介绍二. 语法格式及常用选项三. 参考案例3.1 下载单个文件3.2 使用wget -o 下载文件并改名3.3 -c 参数,下载断开链接时,可以恢复下载3.4 wget后台下载3.5 使用wget下载整个网站四. 补充与汇总常见用法总结前言 大家好ÿ…...
学习ROS时针对gazebo相关的问题(重装与卸载是永远的神)
ResourceNotFound:gazebo_ros 错误解决 参考:https://blog.csdn.net/weixin_42591529/article/details/123869969 当将机器人加载到gazebo时,运行launch文件出现如下错误 这是由于缺少gazebo包所导致的。 解决办法:...
几个C语言容易忽略的问题
1 取模符号自增问题 我们不妨尝试写这样的程序 #include<stdio.h> int main(){int n,t5;printf("%d\n",7%(-3));//1printf("%d\n",(-7)%3);//-1while(--t)printf("%d\n",t);t5;while(t--)printf("%d\n",t);return 0; } 运行…...
CentOS 7.9安装Zabbix 4.4《保姆级教程》
CentOS 7.9安装Zabbix 4.4一、配置一览二、环境准备设置Selinux和firewalld设置软件源1.配置ustc CentOS-Base源2.安装zabbix 4.4官方源3.安装并更换epel源4.清除并生成缓存三、安装并配置Zabbix Server安装zabbix组件安装php安装mariadb并创建数据库修改zabbix_server.conf设置…...
路由器与交换机的区别(基础知识)
文章目录交换机路由器路由器和交换机的区别(1)工作层次不同(2)数据转发所依据的对象不同(3)传统的交换机只能分割冲突域,不能分割广播域;而路由器可以分割广播域(4&#…...
Python基础学习9——函数
基本概念 函数是一种能够完成某项任务的封装工具。在数学中,函数是自变量到因变量的一种映射,通过某种方式能够使自变量的值变成因变量的值。其实本质上也是实现了某种值的转换的任务。 函数的定义 在python中,函数是利用def来进行定义&am…...
项目中的MD5、盐值加密
首先介绍一下MD5,而项目中用的是MD5和盐值来确保密码的安全性; 1. md5简介 md5的全称是md5信息摘要算法(英文:MD5 Message-Digest Algorithm ),一种被广泛使用的密码散列函数,可以产生一个128位…...
电商项目后端框架SpringBoot、MybatisPlus
后端框架基础 1.代码自动生成工具 mybatis-plus (1)首先需要添加依赖文件 <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.2</version></dependency><de…...
2023年03月IDE流行度最新排名
点击查看最新IDE流行度最新排名(每月更新) 2023年03月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多,这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…...
华为校招机试 - 数组取最小值(Java JS Python)
目录 题目描述 输入描述 输出描述 用例 题目解析 JavaScript算法源码 Java算法源码...
20 客户端服务订阅的事件机制剖析
Nacos客户端服务订阅的事件机制剖析 我们已经分析了Nacos客户端订阅的核心流程:Nacos客户端通过一个定时任务,每6秒从注册中心获取实例列表,当发现实例发生变化时,发布变更事件,订阅者进行业务处理,然后更…...
ThreadPoolExecutor中的addWorker方法
在看线程池源码的时候看到了这么一段代码 private boolean addWorker(Runnable firstTask, boolean core) {retry:for (int c ctl.get();;) {// Check if queue empty only if necessary.if (xxx)return false;for (;;) {if (xxx)return false;if (xxx)break retry;if (xxx)c…...
9 有线网络的封装
概述 IPC设备一般都带有网口,支持以有线网络方式接入NVR和其他平台。有线网络的使用比较简单,主要操作有:设置IP地址、子网掩码、网关、DHCP等。在封装有线网络前,我们需要先封装DHCP客户端管理类,用于管理各种网络的DHCP功能。 DHCP客户端管理类 DHCP客户端管理类的头文件…...
Linux----网络基础(2)--应用层的序列化与反序列化--守护进程--0226
文章中有使用封装好的头文件,可以在下面连接处查询。 Linux相关博文中使用的头文件_Gosolo!的博客-CSDN博客 1. 应用层 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层 1.2 协议 我们在之前的套接字编程中使用的是…...
uipath实现滑动验证码登录
现实需求 在进行RPA流程设计过程中,遇到登录系统需要滑动验证的情况,如图所示: 此时需要在RPA流程设计中,借助现有的活动完成模拟人工操作,完成验证登录操作。 设计思路 这个功能流程的设计思路大体如下: …...
wordpress商业授权/首页排名优化公司
Activity的四种状态:--Active/Runing 一个新 Activity 启动入栈后,它在屏幕最前端,处于栈的最顶端,此时它处于可见并可和用户交互的激活状态。 --Paused 当 Activity 被另一个透明或者 Dialog 样式的 Activity 覆盖时的状态。此时…...
海沧网站建设/资源网
本文来源为node.js社区附上链接 http://cnodejs.org/topic/5231a630101e574521e45ef8 require 用来加载代码,而 exports 和 module.exports 则用来导出代码。但很多新手可能会迷惑于 exports 和 module.exports 的区别,为了更好的理解 exports 和 module…...
做外贸网站选美国服务器的费用/电商的运营模式有几种
过滤器的区别 捕捉过滤器(CaptureFilters):用于决定将什么样的信息记录在捕捉结果中。需要在开始捕捉前设置。显示过滤器(DisplayFilters):在捕捉结果中进行详细查找。他们可以在得到捕捉结果后随意修改。那…...
WordPress网站关闭插件/5118网站如何使用免费版
昨天面试上来就是一个算法,平时基本的算法还行,结果变个法就不会了。。。感觉应该刷一波Leecode冷静下。。。今天抽空看下。题目就是要求O(n)复杂度求无序列表中第K的大元素如果没有复杂度的限制很简单。。。加了O(n)复杂度确实有点蒙虽然当时面试官说思…...
温州市建设小学大南网站/南宁关键词排名公司
5 类型参数列表我们学习了模板函数和模板类的定义和使用。但是,看到的“类型形参表”都只有一个元素,那么,如果要定义多个类型,应该怎么样操作?在telplate<类型形参表>中,“类型形参表”可以定义多个…...
网页设计师必须知道的网站/汕头seo服务
HQChart使用教程29-走势图如何对接第3方数据6-websocket分钟数据介绍流程图部分代码设置网络协议回调获取分时图实例和更新回调websocket接收到数据注意点demo地址介绍 hqchart内部没有websocket数据源,所有只能通过外部数据源挂接的方式,挂接第3方webs…...