java 并发的原子性_Java并发教程–原子性和竞争条件

java 并发的原子性

原子性是多线程程序中的关键概念之一。 我们说一组动作是原子的,如果它们都以不可分割的方式作为单个操作执行。 认为多线程程序中的一组操作将被串行执行是理所当然的,可能会导致错误的结果。 原因是由于线程干扰,这意味着如果两个线程对同一数据执行多个步骤,则它们可能会重叠。

下面的交织示例显示了两个线程执行多个操作(循环中的打印)以及它们如何重叠:



public class Interleaving {public void show() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + " - Number: " + i);}}public static void main(String[] args) {final Interleaving main = new Interleaving();Runnable runner = new Runnable() {@Overridepublic void run() {main.show();}};new Thread(runner, "Thread 1").start();new Thread(runner, "Thread 2").start();}
}

执行时,将产生不可预测的结果。 举个例子:

Thread 2 - Number: 0
Thread 2 - Number: 1
Thread 2 - Number: 2
Thread 1 - Number: 0
Thread 1 - Number: 1
Thread 1 - Number: 2
Thread 1 - Number: 3
Thread 1 - Number: 4
Thread 2 - Number: 3
Thread 2 - Number: 4

在这种情况下,不会出错,因为它们只是打印数字。 但是,当您需要在不同步的情况下共享对象的状态(其数据)时,这会导致竞争条件的存在。

比赛条件

如果由于线程交织而有可能产生不正确的结果,则您的代码将处于竞争状态。 本节描述了两种竞争条件:

  1. 先检查后行动
  2. 读-修改-写

为了消除竞争条件并增强线程安全性,我们必须通过使用同步使这些操作成为原子操作。 以下各节中的示例将显示这些竞争条件的影响。

先检查后实际比赛条件

当您有一个共享字段并期望顺序执行以下步骤时,会出现此竞争条件:

  1. 从字段获取值。
  2. 根据上一次检查的结果来做一些事情。

这里的问题是,当第一个线程在上次检查后要执行操作时,另一个线程可能已经插入并更改了字段的值。 现在,第一个线程将基于不再有效的值执行操作。 通过示例更容易看出这一点。

UnsafeCheckThenAct应该一次更改字段 。 在调用changeNumber方法之后,应导致执行else条件:

public class UnsafeCheckThenAct {private int number;public void changeNumber() {if (number == 0) {System.out.println(Thread.currentThread().getName() + " | Changed");number = -1;}else {System.out.println(Thread.currentThread().getName() + " | Not changed");}}public static void main(String[] args) {final UnsafeCheckThenAct checkAct = new UnsafeCheckThenAct();for (int i = 0; i < 50; i++) {new Thread(new Runnable() {@Overridepublic void run() {checkAct.changeNumber();}}, "T" + i).start();}}
}

但是由于此代码未同步,因此(无法保证)可能会导致对该字段进行多次修改:

T13 | Changed
T17 | Changed
T35 | Not changed
T10 | Changed
T48 | Not changed
T14 | Changed
T60 | Not changed
T6 | Changed
T5 | Changed
T63 | Not changed
T18 | Not changed

这种竞争条件的另一个示例是延迟初始化 。

解决此问题的一种简单方法是使用同步。

SafeCheckThenAct是线程安全的,因为它已通过同步对共享字段的所有访问来消除竞争条件。

public class SafeCheckThenAct {private int number;public synchronized void changeNumber() {if (number == 0) {System.out.println(Thread.currentThread().getName() + " | Changed");number = -1;}else {System.out.println(Thread.currentThread().getName() + " | Not changed");}}public static void main(String[] args) {final SafeCheckThenAct checkAct = new SafeCheckThenAct();for (int i = 0; i < 50; i++) {new Thread(new Runnable() {@Overridepublic void run() {checkAct.changeNumber();}}, "T" + i).start();}}
}

现在,执行此代码将始终产生相同的预期结果。 只有一个线程会更改该字段:

T0 | Changed
T54 | Not changed
T53 | Not changed
T62 | Not changed
T52 | Not changed
T51 | Not changed
...

在某些情况下,还有其他机制会比同步整个方法更好,但是我不会在本文中讨论它们。

读-修改-写竞争条件

在这里,当执行以下一组操作时,会出现另一种竞争条件:

  1. 从字段中获取值。
  2. 修改值。
  3. 将新值存储到该字段。

在这种情况下,还有另一种危险的可能性,那就是丢失了对该字段的某些更新。 一种可能的结果是:

Field’s value is 1.
Thread 1 gets the value from the field (1).
Thread 1 modifies the value (5).
Thread 2 reads the value from the field (1).
Thread 2 modifies the value (7).
Thread 1 stores the value to the field (5).
Thread 2 stores the value to the field (7).

如您所见,值5的更新丢失了。

让我们看一个代码示例。 UnsafeReadModifyWrite共享一个数字字段,每次都会递增:

public class UnsafeReadModifyWrite {private int number;public void incrementNumber() {number++;}public int getNumber() {return this.number;}public static void main(String[] args) throws InterruptedException {final UnsafeReadModifyWrite rmw = new UnsafeReadModifyWrite();for (int i = 0; i < 1_000; i++) {new Thread(new Runnable() {@Overridepublic void run() {rmw.incrementNumber();}}, "T" + i).start();}Thread.sleep(6000);System.out.println("Final number (should be 1_000): " + rmw.getNumber());}
}

您能发现引起比赛状况的复合动作吗?

我敢肯定你做到了,但是为了完整起见,我还是会解释一下。 问题出在增量( number ++ )中。 这似乎是一个动作,但实际上,它是三个动作的序列(get-increment-write)。

执行此代码时,我们可能会看到丢失了一些更新:

2014-08-08 09:59:18,859|UnsafeReadModifyWrite|Final number (should be 10_000): 9996

由于无法保证线程如何交织,因此取决于您的计算机,将很难重现此更新丢失。 如果无法重现上面的示例,请尝试UnsafeReadModifyWriteWithLatch ,它使用CountDownLatch来同步线程的启动,并重复测试一百次。 您可能应该在所有结果中看到一些无效值:

Final number (should be 1_000): 1000
Final number (should be 1_000): 1000
Final number (should be 1_000): 1000
Final number (should be 1_000): 997
Final number (should be 1_000): 999
Final number (should be 1_000): 1000
Final number (should be 1_000): 1000
Final number (should be 1_000): 1000
Final number (should be 1_000): 1000
Final number (should be 1_000): 1000
Final number (should be 1_000): 1000

这个例子可以通过使所有三个动作原子化来解决。

SafeReadModifyWriteSynchronized在对共享字段的所有访问中使用同步:

public class SafeReadModifyWriteSynchronized {private int number;public synchronized void incrementNumber() {number++;}public synchronized int getNumber() {return this.number;}public static void main(String[] args) throws InterruptedException {final SafeReadModifyWriteSynchronized rmw = new SafeReadModifyWriteSynchronized();for (int i = 0; i < 1_000; i++) {new Thread(new Runnable() {@Overridepublic void run() {rmw.incrementNumber();}}, "T" + i).start();}Thread.sleep(4000);System.out.println("Final number (should be 1_000): " + rmw.getNumber());}
}

让我们看另一个删除此竞争条件的示例。 在这种特定情况下,由于字段号与其他变量无关,因此我们可以使用原子变量。

SafeReadModifyWriteAtomic使用原子变量来存储字段的值:

public class SafeReadModifyWriteAtomic {private final AtomicInteger number = new AtomicInteger();public void incrementNumber() {number.getAndIncrement();}public int getNumber() {return this.number.get();}public static void main(String[] args) throws InterruptedException {final SafeReadModifyWriteAtomic rmw = new SafeReadModifyWriteAtomic();for (int i = 0; i < 1_000; i++) {new Thread(new Runnable() {@Overridepublic void run() {rmw.incrementNumber();}}, "T" + i).start();}Thread.sleep(4000);System.out.println("Final number (should be 1_000): " + rmw.getNumber());}
}

以下帖子将进一步说明锁定或原子变量之类的机制。

结论

这篇文章解释了在非同步多线程程序中执行复合操作时隐含的一些风险。 为了强制执行原子性并防止线程交织,必须使用某种类型的同步。

  • 您可以在github上查看源代码。

翻译自: https://www.javacodegeeks.com/2014/08/java-concurrency-tutorial-atomicity-and-race-conditions.html

java 并发的原子性

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

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

相关文章

linux 误删除mysql表能恢复吗,Linux误删数据恢复

引子指在键上飘&#xff0c;难免会湿手套。当你按下shiftdel键后&#xff0c;会不会突然心里凉透&#xff0c;当你执行rm -rf后&#xff0c;会不会马上去搜索哪个国家入境不需要签证。或者你还会遇到如下的情况&#xff1a;root4xem7:~# aliasalias cdrm -rfalias ddocker数据恢…

Apache Camel 3.1 –更多骆驼核心优化(第3部分)

我以前曾在博客中介绍过我们在下一个Camel 3.1版本中所做的优化 博客第1部分 博客第2部分 今天&#xff0c;我想简短介绍一下我们已经完成的最新开发&#xff0c;因为我们准备在本周末或下半年准备好构建和发布Camel 3.1。 从第2部分开始&#xff0c;我们设法在路由过程中将…

mysql error 1114,mysql error 1114 table is full 处理分享

问题描述&#xff1a;一、早上上班收到报警&#xff0c;用户中心某slave不同步。二、查看情况&#xff0c;发现mysql error 1114&#xff0c;The table ‘xxxx’ is full 。。。。。三、检查其他slave&#xff0c;都出现同样问题。四、解决问题方案1、网上解决方案a、修改tmp_…

jvm jinfo 参数_jinfo:JVM运行时配置的命令行浏览

jvm jinfo 参数在最近的一些博客中&#xff08;特别是在对Java EE 7性能调优和优化以及WildFly性能调优的书中的评论中&#xff09;&#xff0c;我引用了自己过去在某些Oracle JDK命令行工具上的博客文章。 令我震惊的是&#xff0c;我从来没有专门解决过漂亮的jinfo工具&#…

matlab int 积不出,matlab – 点积:*命令与循环给出不同的结果

我在Matlab,z和beta中有两个向量.矢量z是117&#xff1a;1 0.430742139435890 0.257372971229541 0.0965909090909091 0.694329541928697 0 0.394960106863064 0 0.100000000000000 1 0.264704325268675 0.387774594078319 0.269207605609567 0.472226643323253 0.750000000000…

49自动化测试中最常见的硒异常

开发人员将始终在编写代码时牢记不同的场景&#xff0c;但是在某些情况下&#xff0c;实现可能无法按预期工作。 相同的原则也适用于测试代码&#xff0c;该代码主要用于测试现有产品的功能&#xff0c;发现错误以及使产品100&#xff05;不受错误影响。 正确地说&#xff0c;…

鹰式价差matlab,鹰式期权:什么叫铁鹰式期权组合,蝶式价差期权?

蝶式期权套利 是利用 交割月份的价差进行 套期获利&#xff0c; 个方向相 反、 共享居中交割月份合约的跨期套利组成。是一种期权策略&#xff0c;风险有限&#xff0c;盈利也有限&#xff0c;是由一手牛市套利和一手熊市套利组合而成的。铁鹰式期权组合是牛市看跌价差期权组合…

angular8 rest_带有Angular JS的Java EE 7 – CRUD,REST,验证–第2部分

angular8 rest这是Angular JS承诺的Java EE 7的后续版本–第1部分 。 花了比我预期更长的时间&#xff08;找到时间来准备代码和博客文章&#xff09;&#xff0c;但是终于到了&#xff01; 应用程序 第1部分中的原始应用程序只是带有分页的简单列表&#xff0c;以及提供列表数…

php如果字符串有1 3 5,Day3-php 字符串1

字符串是由一系列字符组成&#xff0c;在PHP中&#xff0c;字符和字节一样&#xff0c;也就是说&#xff0c;一共有256种不同字符的可能性。1、字符串 定义方法字符串型可以用三种方法定义&#xff1a;单引号形式、双引号形式和Heredoc结构形式。单引号&#xff1a;不会解析变量…

带有Java Pojo作为输入输出示例的AWS Lambda函数

在上一教程中&#xff0c;我们看到了如何使用Java创建AWS Lambda函数&#xff0c;并传递了String作为输入&#xff0c;还返回了String作为Output。如果您是第一次创建lambda函数&#xff0c;我建议先阅读该教程。 在本教程中&#xff0c;我们将看到如何传递Java普通的旧Java对…

bing搜索php,PHP 使用bing搜索网站的api封装类用法

/*** 使用bing api搜索网站的PHP封装类** param* author 编程之家 jb51.cc jb51.cc**/class BingAPI{var $accountKey ;var $ServiceRootURL https://api.datamarket.azure.com/Bing/Search/;var $WebSearchURL;var $searchText;var $searchType;var $request_data;var $Auto…

php右侧弹窗QQ客服,JavaScript_网页右侧悬浮滚动在线qq客服代码示例,网页右侧悬浮滚动QQ在线客服 - phpStudy...

网页右侧悬浮滚动在线qq客服代码示例网页右侧悬浮滚动QQ在线客服代码function myEvent(obj,ev,fn){if (obj.attachEvent){obj.attachEvent(onev,fn);}else{obj.addEventListener(ev,fn,false);};};function getbyClass(id,sClass){var oParent document.getElementById(id);va…

测试Maven版本插件自动递增版本号

抽象 Maven版本插件是一个非常强大的工具&#xff0c;我在很大程度上依赖于它来协调软件版本。 通常&#xff0c;软件发行版本号遵循简单的1.0.0.0-SNAPSHOT格式。 但是最近我需要在版本号中添加限定符-类似于1.0-beta-SNAPSHOT或1.0.0-fix-bug-description-SNAPSHOT 。 在我第…

idea spark java,IntelliJ Idea 搭建spark 开发环境

笔者介绍的是在MAC环境下使用Idea搭建spark环境。环境:spark 2.0.0scala 2.11.8maven 3.9.9idea 151.Idea的安装.Idea可以在官网上下载。熟悉java的肯定都知道这个开发利器&#xff0c;可以在官网上进行下载&#xff0c;在此就不在赘述。有免费的和付费版本&#xff0c;对于我们…

optaplanner_OptaPlanner –具有真实道路距离的车辆路线

optaplanner在现实世界中&#xff0c;车辆路径问题&#xff08;VRP&#xff09;中的车辆必须走这条路&#xff1a;它们不能在客户之间直线行驶。 大多数VRP研究论文和演示都乐于忽略此实现细节。 和我一样&#xff0c;过去。 尽管使用道路距离&#xff08;而不是空中距离&#…

php 转换数组为小写,PHP如何将数组键转换为小写?

在PHP中将数组键转换为小写&#xff0c;我们可以在不使用循环的情况下来简单实现。我们只需要使用array_change_key_case()。array_change_key_case函数有两个参数&#xff0c;一个为数组&#xff0c;另一个可以为常量“CASE_LOWER”&#xff0c;那么我们在做大项目的时候可能会…

带有PostgreSQL的Docker Compose for Spring Boot应用程序

在此博客文章中&#xff0c;您将学习如何使用PostgreSQL配置Spring Boot应用程序以与Docker Compose一起运行。 这篇博客文章涵盖&#xff1a; Spring Boot应用程序Dockerfile配置&#xff0c;在依赖关系和资源之间进行了清晰的分离 用于通过PostgreSQL运行应用程序的Docker…

java中的jpa_JPA教程–在Java SE环境中设置JPA

java中的jpaJPA代表Java Persistence API&#xff0c;它基本上是一个规范&#xff0c;描述了一种将数据持久存储到持久存储&#xff08;通常是数据库&#xff09;中的方法。 我们可以将其视为类似于Hibernate之类的ORM工具的东西&#xff0c;除了它是Java EE规范的正式组成部分…

java中给组合框加监听器,如何区分ActionEvent中的多个组合框

我有两个类&#xff0c;一个包含两个组合框的监听器类和一个监听两个组合框的监听器类。该软件是关于披萨的&#xff0c;而组合框的选择是披萨类型和数量&#xff0c;我会发布相关的代码。(gui class)private Listener listen new Listener();private JComboBox chooseItem n…

使用ArchUnit验证代码和体系结构约束

介绍 ArchUnit是一个用于根据一组自定义代码和体系结构约束检查Java代码的库。 这些约束可以在单元测试中的流畅Java API中定义。 ArchUnit可用于验证类或层之间的依赖关系&#xff0c;检查循环依赖关系等等。 在本文中&#xff0c;我们将创建一些示例规则&#xff0c;以了解如…