RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 616278
Accepted
Ksenia
Ksenia
Asked:2020-01-18 18:01:47 +0000 UTC2020-01-18 18:01:47 +0000 UTC 2020-01-18 18:01:47 +0000 UTC

原子和非原子操作 (java)

  • 772

如何理解哪些操作是原子的,哪些是非原子的?

这是我在 Habré 上找到的:

如果相对于访问该内存的其他线程在一个步骤中完成,则共享内存区域上的操作被称为原子操作。当对变量执行这样的操作时,没有线程可以看到半完成的更改。原子加载确保整个变量在一个时间点被加载。非原子操作不提供这样的保证。

那些。据我了解,原子操作非常小,“相对于其他线程一步完成”。但是这个“步骤”是什么意思呢?

一步==一机操作?或者是其他东西?如何准确地确定哪些操作是原子的,哪些是非原子的?

PS:我发现了一个类似的问题,但它是关于 C#...

java
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Best Answer
    etki
    2020-01-25T10:04:34Z2020-01-25T10:04:34Z

    如何定义原子性?

    操作的原子性通常由其不可分割性的符号表示:操作可以完全应用或根本不应用。一个很好的例子是将值写入数组:

    public class Curiousity {
        public volatile int[] array;
    
        public void nonAtomic() {
            array = new int[1];
            array[0] = 1;
        }
    
        public void probablyAtomic() {
            array = new int[] { 1 };
        }
    }
    

    使用方法nonAtomic时,有可能某个线程在未初始化array[0]时访问并接收到意外值。array[0]使用时probablyAtomic(前提是首先填充数组然后才分配 -我现在不能保证在 ja​​va 中就是这种情况,但想象一下这个规则在示例的框架内有效)这不应该是:数组总是包含null,或初始化数组,但数组 [0] 不能包含除 1 以外的任何内容。此操作是不可分割的,不能像以前那样应用一半nonAtomic- 要么完全要么根本不,其余代码可以安全地预期数组将为 null 或值,而无需诉诸额外的检查。

    此外,操作的原子性通常意味着其结果对其所引用的系统中的所有参与者(在本例中为线程)可见;这是合乎逻辑的,但在我看来,这并不是原子性的强制性标志。

    它为什么如此重要?

    原子性通常源于应用程序的业务需求:银行交易必须全部应用,音乐会门票必须立即按指定数量订购,等等。具体来说,在解析的上下文中(java 中的多线程),任务更原始,但是从相同的需求增长:例如,如果正在编写一个 Web 应用程序,那么解析 HTTP 请求的服务器必须有一个原子附加队列传入请求,否则存在传入请求丢失的风险,从而导致服务质量下降。原子操作提供保证(不可分割性),并且应该在需要这些保证时调用它们。

    此外,原子操作是线性化的——粗略地说,它们的执行可以分解为一个线性历史,而简单的操作可以产生一个历史图,这在某些情况下是不可接受的。

    为什么原始操作本身不是原子的?这对每个人来说也会更容易。

    现代运行时环境非常复杂,并且可以对代码进行大量优化,但在大多数情况下,这些优化会违反保证。由于大多数代码实际上并不需要这些保证,因此将具有特定保证的操作分离到一个单独的类中会更容易,反之亦然。最常见的例子是表达式的重新排序——处理器和 JVM 有权以不同于代码中描述的顺序执行表达式,直到程序员使用具有特定保证的操作强制执行特定顺序。您还可以举一个从内存中读取值的示例(但是我不确定它在形式上是否正确):

    thread #1: set x = 2
    processor #1: save_cache(x, 2)
    processor #1: save_memory(x, 2)
    thread #2: set x = 1
    processor #2: save_cache(x, 1)
    processor #2: save_memory(x, 1)
    thread #1: read x
    processor #1: read_cache(x) = 2 // в то время как х уже был обновлен значением 1 в thread #2
    

    它不使用所谓的。单一事实来源以控制 X 的值,因此这种异常是可能的。据我所知,直接读写内存(或内存和处理器的共享缓存)正是强制使用 volatile 修饰符的原因(我在这里可能是错的)。

    当然,优化后的代码运行速度更快,但绝不应该为了代码性能而牺牲必要的保证。

    这仅适用于与设置变量和其他处理器活动相关的操作吗?

    不。任何操作都可以是原子的或非原子的,例如,经典关系数据库保证一个事务——可能包括对数据的兆字节更改——要么完全应用,要么不应用。处理器指令在这里无关紧要;一个操作可以是原子的,只要它本身是原子的,或者它的结果显示为另一个原子操作(例如,数据库事务的结果显示为对文件的写入)。

    另外,据我了解,“指令在一个周期内没有时间-操作是非原子的”这一说法也是不正确的,因为有一些专门的指令,没有人费心去原子地设置任何值内存在受保护块的入口处,并在出口处将其删除。

    任何操作都可以是原子的吗?

    不。我非常缺乏正确表述的资格,但据我了解,任何涉及两个或多个外部影响(副作用)的操作都不能按定义是原子的。副作用主要是指与某些外部系统(无论是文件系统还是外部 API)交互,但即使是同步块内的两个变量设置表达式也不能被视为原子操作,直到其中一个可以抛出异常 - 而这个,鉴于 OutOfMemoryError 和其他可能的结果,可能根本不可能。

    我的手术有两种或多种副作用。我还能做些什么吗?

    是的,可以创建一个保证所有操作都将被应用的系统,但条件是任何副作用都可以无限次调用。您可以创建一个日志系统,自动记录计划的操作,定期检查日志,并执行尚未应用的操作。这可以表示如下:

    client: journal.push {withdrawMoney {card=41111111, cvc=123}, reserveTicket {concert=123}, sendEmail {address=nobody@localhost}}
    client: <журнал подтвердил получение и запись задания>
    journal: process withdrawMoney
    journal: markCompleted withdrawMoney
    journal: process reserveTicket
    journal: <умирает, не успев записать выполнение reserveTicket>
    journal: <восстанавливается>
    journal: process reserveTicket # сайд-эффект вызывается еще раз, но только в случае некорректной работы
    journal: markCompleted reserveTicket
    journal: process sendEmail
    journal: markCompleted sendEmail
    

    这确保了算法的进展,但消除了时间范围内的所有义务(从形式上讲,无论如何并不是一切都井井有条)。如果操作是幂等的,那么这样的系统迟早会达到所需的状态,与预期的状态没有任何明显的差异(执行时间除外)。

    你如何确定java中操作的原子性?

    在这种情况下,事实的主要来源是 Java 内存模型,它定义了哪些假设和保证适用于 JVM 中的代码。然而Java内存模型理解起来相当复杂,涵盖的操作范围比原子操作的范围大得多,所以在本题的上下文中,知道volatile修饰符提供原子读写就足够了,并且 Atomic* 类允许您执行比较和交换操作以原子地更改值,而不必担心另一个值会介于读取和写入之间,并且在下面的评论中,在阅读时,他们可能添加了其他内容我忘了。

    • 29
  2. Barmaley
    2020-01-24T19:58:52Z2020-01-24T19:58:52Z

    如何理解哪些操作是原子的,哪些是非原子的?

    冒着招致性别歧视指责的风险,我无法抗拒并举一个原子操作的例子:怀孕是一个严格的原子操作,永远只有一个父亲(我们会把各种遗传技巧排除在外)括号)。

    反之亦然,非原子操作的一个例子:唉,抚养孩子是一个非原子操作,不幸的是,孩子是孩子脆弱灵魂上许多不同的不同步操作的主题:妈妈,爸爸,祖母,祖父,僵尸,幼儿园,学校,朋友,女朋友等。按清单。

    • 18
  3. Artem Konovalov
    2020-01-18T18:23:30Z2020-01-18T18:23:30Z

    我会尽力解释。我可能是错的。

    有java,源代码被编译成字节码。字节码在程序执行期间被转换成机器码。字节码中的一条指令/命令可以翻译成多条机器码指令。这就是原子性的问题。处理器不能一次执行一条用高级语言编写的指令:它执行包含一系列指令的机器码。因此,如果不同的处理器对相同的数据进行操作,那么处理器的不同指令可以交错。

    我举个例子:

    有一个全局变量:

    public volatile int value = 0;
    
    first-thread {
        value++
    }
    
    second-thread{
        value++ 
    }
    

    递增变量不是原子操作:它至少需要三个指令:

    • 读取数据
    • 增加一
    • 写数据

    因此,两个线程必须执行这个序列,但它们之间的执行顺序没有定义。因此,可能会出现如下情况:

    1. 第一个线程读取数据
    2. 第二个线程读取数据
    3. 第一个线程将值递增 1
    4. 第二个线程将值增加 1
    5. 第二个线程写值
    6. 第一个线程写入值

    结果,我们得到的结果是 1,而不是预期的 2。

    为了防止这种情况发生,使用了包中的同步或原子原语java.util.concurrent

    • 10

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5