Java的for循环有没有针对线程安全的优化?

2024-03-30

我有一段代码可以更改两个线程中的计数器。它不是线程安全的,因为我没有在代码中放置任何原子变量或锁。如果代码只运行一次,它会给出正确的结果,但我想运行它多次,所以我将代码放入 for 循环中。问题是只有第一个或前两个循环才会生成我期望的结果。对于其余的循环,结果始终为 0,这似乎是线程安全的。 Java虚拟机中是否有任何内部运算符导致这样的事情?

我尝试过改变循环数,前一两个总是我所期望的,但无论有多少个循环,其他都是0。

Counter:

private static class Counter {
    private int count;

    public void increase() {
        count++;
    }

    public void decrease() {
        count--;
    }

    public int getCount() {
        return count;
    }
}

Person:

// This is just a thread to increase and decrease the counter for many times.
private static class Person extends Thread {
    private Counter c;

    public Person(Counter c) {
        this.c = c;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            c.increase();
            c.decrease();
        }
    }
}

主要方法:

public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        Counter c = new Counter();
        Person p1 = new Person(c);
        Person p2 = new Person(c);
        p1.start();
        p2.start();
        p1.join();
        p2.join();
        System.out.println("run "+i+": "+c.getCount());        
   }
}

Output:

run 0: 243
run 1: 12
run 2: 0
run 3: 0
run 4: 0
run 5: 0
run 6: 0
run 7: 0
run 8: 0
run 9: 0

我不知道为什么其余的结果总是0。但我猜测这是关于JVM的优化。当某些循环完成后,JVM 会优化代码,并忽略其余循环并始终给出 0 作为答案,这是正确的吗?


这发生了令人惊讶的转变。

首先可以说(相对肯定)的是,该效果是由 JIT 引起的。我将代码片段合并到这个 MCVE 中:

public class CounterJitTest
{
    private static class Counter
    {
        private int count;

        public void increase()
        {
            count++;
        }

        public void decrease()
        {
            count--;
        }

        public int getCount()
        {
            return count;
        }
    }

    private static class Person extends Thread
    {
        private Counter c;

        public Person(Counter c)
        {
            this.c = c;
        }

        @Override
        public void run()
        {
            for (int i = 0; i < 1000000; i++)
            {
                c.increase();
                c.decrease();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        for (int i = 0; i < 10; i++)
        {
            Counter c = new Counter();
            Person p1 = new Person(c);
            Person p2 = new Person(c);
            p1.start();
            p2.start();
            p1.join();
            p2.join();
            System.out.println("run " + i + ": " + c.getCount());
        }
    }
}

运行它

java CounterJitTest

导致问题中提到的输出:

run 0: 6703
run 1: 178
run 2: 1716
run 3: 0
run 4: 0
run 5: 0
run 6: 0
run 7: 0
run 8: 0
run 9: 0

关闭 JIT-Xint(解释模式),即启动为

java -Xint CounterJitTest

导致以下结果:

run 0: 38735
run 1: 53174
run 2: 86770
run 3: 27244
run 4: 61885
run 5: 1746
run 6: 32458
run 7: 52864
run 8: 75978
run 9: 22824

为了更深入地了解 JIT 的实际含义does,我在 HotSpot 反汇编器虚拟机中启动了整个过程,以查看生成的程序集。然而,执行时间太快了,我想:好吧,我只需增加计数器for-loop:

for (int i = 0; i < 1000000; i++)

但即使增加到100000000导致程序立即完成。这已经引起了怀疑。生成反汇编后

java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly -XX:+PrintInlining CounterJitTest

我查看了编译版本increase and decrease方法,但没有发现任何明显的东西。但是,那run方法似乎是这里的罪魁祸首。最初,组装run方法包含预期的代码(此处仅发布最相关的部分):

Decoding compiled method 0x0000000002b32fd0:
Code:
[Entry Point]
[Constants]
  # {method} {0x00000000246d0f00} &apos;run&apos; &apos;()V&apos; in &apos;CounterJitTest$Person&apos;
  ...
[Verified Entry Point]
  ...
  0x0000000002b33198: je     0x0000000002b33338  ;*iconst_0
            ; - CounterJitTest$Person::run@0 (line 35)

  0x0000000002b3319e: mov    $0x0,%esi
  0x0000000002b331a3: jmpq   0x0000000002b332bc  ;*iload_1
            ; - CounterJitTest$Person::run@2 (line 35)

  0x0000000002b331a8: mov    0x178(%rdx),%edi   ; implicit exception: dispatches to 0x0000000002b3334f
  0x0000000002b331ae: shl    $0x3,%rdi          ;*getfield c
            ; - CounterJitTest$Person::run@9 (line 37)

  0x0000000002b331b2: cmp    (%rdi),%rax        ;*invokevirtual increase
            ; - CounterJitTest$Person::run@12 (line 37)
            ; implicit exception: dispatches to 0x0000000002b33354
  ...
  0x0000000002b33207: je     0x0000000002b33359
  0x0000000002b3320d: mov    0xc(%rdi),%ebx     ;*getfield count
            ; - CounterJitTest$Counter::increase@2 (line 9)
            ; - CounterJitTest$Person::run@12 (line 37)

  0x0000000002b33210: inc    %ebx
  0x0000000002b33212: mov    %ebx,0xc(%rdi)     ;*putfield count
            ; - CounterJitTest$Counter::increase@7 (line 9)
            ; - CounterJitTest$Person::run@12 (line 37)
  ...
  0x0000000002b3326f: mov    %ebx,0xc(%rdi)     ;*putfield count
            ; - CounterJitTest$Counter::decrease@7 (line 14)
            ; - CounterJitTest$Person::run@19 (line 38)

  ...

诚然,我并不深刻地“理解”这一点,但人们可以看到它确实起到了作用getfield c,以及(部分内联?)的一些调用increase and decrease方法。

但是,那final的编译版本run方法是这样的:

Decoding compiled method 0x0000000002b34590:
Code:
[Entry Point]
[Constants]
  # {method} {0x00000000246d0f00} &apos;run&apos; &apos;()V&apos; in &apos;CounterJitTest$Person&apos;
  #           [sp+0x20]  (sp of caller)
  0x0000000002b346c0: mov    0x8(%rdx),%r10d
  0x0000000002b346c4: 
<writer thread='2060'/>
[Loaded java.lang.Shutdown from C:\Program Files\Java\jre1.8.0_131\lib\rt.jar]
<writer thread='5944'/>
shl    $0x3,%r10
  0x0000000002b346c8: cmp    %r10,%rax
  0x0000000002b346cb: jne    0x0000000002a65f60  ;   {runtime_call}
  0x0000000002b346d1: data32 xchg %ax,%ax
  0x0000000002b346d4: nopw   0x0(%rax,%rax,1)
  0x0000000002b346da: nopw   0x0(%rax,%rax,1)
[Verified Entry Point]
  0x0000000002b346e0: mov    %eax,-0x6000(%rsp)
  0x0000000002b346e7: push   %rbp
  0x0000000002b346e8: sub    $0x10,%rsp         ;*synchronization entry
            ; - CounterJitTest$Person::run@-1 (line 35)

  0x0000000002b346ec: cmp    0x178(%rdx),%r12d
  0x0000000002b346f3: je     0x0000000002b34701
  0x0000000002b346f5: add    $0x10,%rsp
  0x0000000002b346f9: pop    %rbp
  0x0000000002b346fa: test   %eax,-0x1a24700(%rip)        # 0x0000000001110000
            ;   {poll_return}
  0x0000000002b34700: retq   
  0x0000000002b34701: mov    %rdx,%rbp
  0x0000000002b34704: mov    $0xffffff86,%edx
  0x0000000002b34709: xchg   %ax,%ax
  0x0000000002b3470b: callq  0x0000000002a657a0  ; OopMap{rbp=Oop off=80}
            ;*aload_0
            ; - CounterJitTest$Person::run@8 (line 37)
            ;   {runtime_call}
  0x0000000002b34710: int3                      ;*aload_0
            ; - CounterJitTest$Person::run@8 (line 37)

  0x0000000002b34711: hlt    
  0x0000000002b34712: hlt    
  0x0000000002b34713: hlt    
  0x0000000002b34714: hlt    
  0x0000000002b34715: hlt    
  0x0000000002b34716: hlt    
  0x0000000002b34717: hlt    
  0x0000000002b34718: hlt    
  0x0000000002b34719: hlt    
  0x0000000002b3471a: hlt    
  0x0000000002b3471b: hlt    
  0x0000000002b3471c: hlt    
  0x0000000002b3471d: hlt    
  0x0000000002b3471e: hlt    
  0x0000000002b3471f: hlt    
[Exception Handler]
[Stub Code]
  0x0000000002b34720: jmpq   0x0000000002a8c9e0  ;   {no_reloc}
[Deopt Handler Code]
  0x0000000002b34725: callq  0x0000000002b3472a
  0x0000000002b3472a: subq   $0x5,(%rsp)
  0x0000000002b3472f: jmpq   0x0000000002a67200  ;   {runtime_call}
  0x0000000002b34734: hlt    
  0x0000000002b34735: hlt    
  0x0000000002b34736: hlt    
  0x0000000002b34737: hlt    

这是complete组装方法!它确实……嗯,基本上什么也没做。

为了证实我的怀疑,我明确地disabled的内联increase方法,从开始

java -XX:CompileCommand=dontinline,CounterJitTest$Counter.increase CounterJitTest

输出再次符合预期:

run 0: 3497
run 1: -71826
run 2: -22080
run 3: -20893
run 4: -17
run 5: -87781
run 6: -11
run 7: -380
run 8: -43354
run 9: -29719

所以我的结论是:

JIT 内联了increase and decrease方法。它们仅递增和递减相同的值。内联之后,JIT 足够聪明,可以找出调用的顺序

c.increase();
c.decrease();

本质上是一个no-op,因此,就是这样做:什么也没有。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java的for循环有没有针对线程安全的优化? 的相关文章

随机推荐

  • 如何忽略缩放设置

    IE忽略缩放设置不起作用 我的代码如下 为什么它不起作用 我收到错误消息 selenium common exceptions SessionNotCreatedException 消息 启动 Internet Explorer 时出现意外
  • 带有内部 select 子句和 group by 汇总的 sql order by

    我的查询如下所示 输出 电流输出 Role Cases prepped Completed State Member 1 10 5 50 State Member 2 10 7 70 State President 10 2 20 Summ
  • header/impl 关键字在 kotlin 中的含义是什么?

    就在不久前 当我探索 Kotlin github 存储库时 我发现了一些有趣的事情 Kotlin 现在似乎有一个 header 关键字 收藏H kt https github com JetBrains kotlin blob be95f6
  • string.Empty 与 "" - 这有变化吗?

    根据这个答案 https stackoverflow com a 2905422 386869 and string Empty略有不同 因为 创建一个对象 而string Empty才不是 该答案在该问题上得票最多 然而 这个答案 htt
  • 在实体框架的 where 子句中使用列表

    我正在尝试通过一对多表检索文档 ID 我想在 where 子句中使用列表来查找与列表中每个元素相关的所有 id List
  • Magento group by 日期字段上的子句

    我需要获取指定月份一天内 grand total 的订单总数 SUM MIN MAX 和 AVG 这就是我正在做的事情 collection gt getSelect gt columns SUM base grand total AS t
  • Kotlin - StateFlow 不向其收集器发出更新

    我的应用程序中有一个 UserStateModel 数据类 类型的 StateFlow private val userStateFlow MutableStateFlow
  • TextView 作为带有文本颜色操作的进度条?

    我正在努力改进我的应用程序的用户界面 在我使用的设计中 我有一个 TextView 它将在特定时间充当进度条 ruslt 应该看起来像这样 问题是 随着进度的变化 部分文本的颜色也会改变 我研究了android中的spannablestri
  • ASP.NET Web API 中是否有等效的@JsonView

    我在 Spring 和 Java 方面有更多的经验 但现在我正在从事 ASP NET Web API 项目 因此 在 Spring 中 我可以使用 JsonView 注释来注释我的 DTO 这样我就可以选择通过 REST 显示哪些数据 我发
  • 如何在 web.config 文件中存储字典对象?

    我想在我的网络配置文件中存储一个简单的键 值字符串字典 Visual Studio 可以轻松存储字符串集合 请参阅下面的示例 但我不确定如何使用字典集合来存储字符串集合
  • Cocoa-Touch:如何查看两个 NSDate 是否在同一天?

    我需要知道两个 NSDate 实例是否都来自同一天 有没有比获取 NSDateComponents 并比较日 月 年更简单 更好的方法 如果您的目标是iOS 8 和 OS X 10 9 或更高版本 then 乔的回答 https stack
  • libQt5XcbQpa.so.5:未定义的符号:FT_Property_Set

    当我尝试从 python 脚本运行 wkhtmltopdf 命令时出现此错误 usr bin wkhtmltopdf symbol lookup error usr lib x86 64 linux gnu libQt5XcbQpa so
  • 嵌套视图中的拥抱和压缩阻力

    我试图了解拥抱和抗压阻力是如何真正发挥作用的 我有这样的场景 我需要左侧两个标签 绿色容器内 和右侧两个标签 蓝色容器内 如图所示 我希望绿色容器能够容纳内容 Android 的wrap content 和蓝色容器来填充剩余空间 Andro
  • PostgreSQL 中的跨数据库查询[重复]

    这个问题在这里已经有答案了 我正在尝试在 Postgres 中构建查询 我的背景是 SQL Server 因此我遇到了一些语法挑战 我的查询需要访问两个独立服务器上的两个独立数据库 我需要在数据集之间进行连接 本质上 我在 db1 中有一个
  • pySerial 与 python 2.7 和 3.4 的差异

    我正在开发一个项目 需要通过串口将一些数字从 Windows 10 中的 python 发送到 arduino uno 作为一个简单的测试 我只想通过发送 2 来打开 LED 并通过从命令提示符发送 4 来关闭 LED 尽管我希望最终能够将
  • ScrollView 只能承载一个直接子异常

    我想将图像添加到滚动视图 我尝试使用以下代码 ScrollView sv ScrollView findViewById R id scrollView2 ImageView iv new ImageView this iv setImag
  • Android Studio - Flutter 插件未安装;这增加了 Flutter 特定的功能

    考虑到我已经遵循了所有这些步骤https flutter dev docs get started install linux https flutter dev docs get started install linux在我的 Ubun
  • 使用空格键启动和停止 Python 海龟

    我正在尝试编写一个程序 通过按空格键来启动和停止乌龟 我得到了启动乌龟移动的代码 但当我再次按下它时它并没有停止 看来只是提高速度而已 这是我的编码要求和我输入的代码 创建一个包含三个函数的乌龟程序来控制乌龟 创建一个名为 turnLeft
  • Twitter Bootstrap Carousel 的表达式引擎频道条目无法正常工作

    好的 我在通过 ExpressionEngine 的频道条目实现基本的 Twitter 引导轮播时遇到问题 我认为这与以下事实有关 您必须将第一个 li 标记指定为 活动 以便引导程序知道页面加载时显示哪个图像 但我的代码如下 div cl
  • Java的for循环有没有针对线程安全的优化?

    我有一段代码可以更改两个线程中的计数器 它不是线程安全的 因为我没有在代码中放置任何原子变量或锁 如果代码只运行一次 它会给出正确的结果 但我想运行它多次 所以我将代码放入 for 循环中 问题是只有第一个或前两个循环才会生成我期望的结果