上一章我们了解了原子操作Interlocked类的设计原理及简单介绍,今天我们将对Interlocked的使用进行详细讲解。

在此之前我们先学习一个概念——原子操作。

01、Read方法

该方法用于原子的读取64位值,有分别针对long类型和ulong类型的两个重载方法;

对于64位系统,64位数据类型的读取本身就是原子操作;而对于32位系统,64位数据类型的读取需要至少两个原子指令,因此在32位系统可以通过Read方法对64位数据类型进行原子读取。

用法也很简单,示例如下:

private static long _readValue = 0;
public static void ReadRun()
{
var thread = new Thread(ModifyReadValue);
thread.Start();
Thread.Sleep(100);
var value = Interlocked.Read(ref _readValue);
Console.WriteLine("原子读取long类型变量: " + value);
}
static void ModifyReadValue()
{
_readValue = 88;
Console.WriteLine("修改long类型变量: " + _readValue);
}

运行结果如下:

因为系统环境原因无法模拟出32位系统效果,因此这里只是给了个简单使用示例。

02、Increment方法

该方法用于原子的递增指定的变量,并返回递增后的新值。该方法有4个重载方法,分别为long、ulong、int和uint四种数据类型;该方法适用于多线程环境中需要安全递增变量的场景,如计数器、资源管理等。

对于加法操作,无论是i+1,还是i++或++i,都不是线程安全的,最终可能会生成3条CPU指令,整个操作过程大致如下:

1.将 i 的值加载到寄存器,即从内存中读取i;

2.将寄存器中值加1,即i值加1;

3.最后将寄存器中值回写到i,即完成i值的变更;

而在这编码层面为1行代码,而CPU层面为3行指令的操作中,随时都有可能被线程调度器打断,而导致其他线程同时对i进行操作,最终导致竞争条件,最后数据错乱。

下面我们来举个例子,启动100个线程,分别对一个共享变量进行1000次递增1,最后打印出共享变量,运行这个示例9次观察每次运行结果,代码如下:

private static long _incrementValue = 0;
public static void IncrementRun()
{
//运行9次测试,观察每次结果
for (var i = 1; i < 10; i++)
{
//启动100个线程,对变量进行递增
var threads = new Thread[100];
for (var j = 0; j < threads.Length; j++)
{
threads[j] = new Thread(ModifyIncrementValue);
threads[j].Start();
}
//等待所有线程执行完成
foreach (var thread in threads)
{
thread.Join();
}
//最后打印结果
Console.WriteLine($"第 {i} 运行结果: {_incrementValue}");
_incrementValue = 0;
}
}
static void ModifyIncrementValue()
{
for (var i = 0; i < 1000; i++)
{
++_incrementValue;
}
}

先看下执行结果:

可以发现每次的运行结果都不相同,并且结果也不对。这就是因为++i操作并不是原子操作,是线程不安全的。

只需要把上面代码:

++_incrementValue;

改为:

Interlocked.Increment(ref _incrementValue);

即可解决上面的问题,修改过后,我们再来看看执行结果:

03、Decrement方法

该方法用于原子的递减指定的变量,并返回递减后的新值。该方法同样有4个重载方法,分别为long、ulong、int和uint四种数据类型;

该方法和Increment方法基本一样,区别就是一个是递增一个是递减,因此用法可以直接参考Increment方法,这里就不做详细讲解了。

04、Add方法

该方法用于原子的对两个变量求和,将第一个变量替换为两者和,并返回操作后第一个变量的新值。该方法同样有4个重载方法,分别为long、ulong、int和uint四种数据类型;

虽然这个方法叫求和是加法,但是只需要把第2个参数变为负数,既可以实现减法。简单来说该方法可以实现原子的对两个变量求和与求差。

上面Increment方法和Decrement方法,只能对变量每次进行递增递减1,而能随意加减,可以通过Add方法实现两个变量进行加减。

下面我们用代码实现累加和累减示例用来说明Add使用方法,就不展示线程安全差异了,可以参考Increment方法中的示例,自己写一个线程不安全的示例。

private static long _addValue = 0;
public static void AddRun()
{
for (var j = 0; j < 1000; j++)
{
//_addValue =_ addValue + j;
Interlocked.Add(ref _addValue, j);
}
Console.WriteLine($"累加结果: {_addValue}");
_addValue = 0;
for (var j = 0; j < 1000; j++)
{
//_addValue =_ addValue - j;
Interlocked.Add(ref _addValue, -j);
}
Console.WriteLine($"累减结果: {_addValue}");
}

执行结果如下:

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

并发编程 - 线程同步(四)之原子操作Interlocked详解一的更多相关文章

  1. Python并发编程-线程同步(线程安全)

    Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...

  2. 33 - 并发编程-线程同步-Event-lock

    目录 1 线程同步 1.1 Event 1.1.1 什么是Flag? 1.1.2 Event原理 1.1.3 吃包子 1.2 Lock 1.2.1 lock方法 1.2.2 计数器 1.2.3 非阻塞 ...

  3. Java并发编程(07):Fork/Join框架机制详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.Fork/Join框架 Java提供Fork/Join框架用于并行执行任务,核心的思想就是将一个大任务切分成多个小任务,然后汇总每个小任务 ...

  4. C#并行编程-线程同步原语

    菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...

  5. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  6. 【转】Java并发编程:同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

  7. 8、Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  8. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  9. [书籍翻译] 《JavaScript并发编程》第四章 使用Generators实现惰性计算

    本文是我翻译<JavaScript Concurrency>书籍的第四章 使用Generators实现惰性计算,该书主要以Promises.Generator.Web workers等技术 ...

  10. java并发编程 线程基础

    java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...

随机推荐

  1. HAR文件

    简介 HAR(HTTP Archive format),是一种或 JSON 格式的存档格式文件,通用扩展名为 .har.Web 浏览器可以使用该格式导出有关其加载的网页的详细性能数据. 使用场景 在开 ...

  2. gitlab之配置文件.gitlab-ci.yml

    自动化部署給我们带来的好处 自动化部署的好处体现在几个方面 1.提高前端的开发效率和开发测试之间的协调效率 Before 如果按照传统的流程,在项目上线前的测试阶段,前端同学修复bug之后,要手动把代 ...

  3. 专为移动端设计的PDF阅读器-小白PDF阅读器

    以前在手机端看扫描版的pdf电子书,要么放大后用手指来回拖动着看,要么将手机横屏看,阅读体验非常差!后来就想自己做一款能自动重排版的pdf阅读器,于是小白PDF阅读器就诞生了! 小白PDF阅读器的重排 ...

  4. fastadmin-表单使用

    1.日期时间 在FastAdmin中的日期时间组件采用的是Bootstrap-datetimepicker插件,官方文档:Bootstrap-datetimepicker官方教程 日期时间格式设置:设 ...

  5. Java基础面试:关键字与注释

    Java 中的关键字 什么是关键字 Java 关键字是 Java 语言中预先定义好的.具有特殊含义的标识符.这些标识符在程序中有固定的用途,不能用作变量名.方法名或类名.Java 中共有 53 个特殊 ...

  6. day02 计算机组成

    day02 计算机组成 1.硬件 计算机硬件是指一些物理装置按照系统结构的要求构成一个有机整体为计算机软件运行提供物质基础 构成最基础的硬件有: CPU 内存 主板 IO设备 2.软件 计算机软件可以 ...

  7. RocketMQ系列3:核心技术介绍

    1 介绍 RocketMQ是一款分布式消息中间件,其核心技术涉及多个核心概念,包括主题.队列.消息.生产者.消费者.消费者分组以及订阅关系.以下是对这些核心技术的详细说明: 2 核心技术实现 2.1 ...

  8. 中电资讯-乘风破浪数字经济,银行如何Hold数据?

    近期各类规划密集发布人行金融科技发展规划发布 金融标准化"十四五"规划发布 "十四五"信息化规划发布 -- 和数据应用有关的各项政策密集出炉 数字经济发展中如何 ...

  9. Docker OCI runtime exec failed: exec failed: container_linux.go:344: starting container process caus

    docker执行命令:docker exec -it 1e33b26152e1 /bin/bash 在进入容器报错:OCI runtime exec failed: exec failed: cont ...

  10. Qt编写手机端视频播放器/推流工具/Onvif工具

    一.视频播放器 同时支持多种解码内核,包括qmedia内核(Qt4/Qt5/Qt6).ffmpeg内核(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6).vlc内核(v ...