【问题标题】:Is the pre-increment operator thread-safe?预增量运算符是线程安全的吗?
【发布时间】:2011-11-30 05:32:06
【问题描述】:

我正在用 Java 编写一个程序,让几辆车互相比赛。每辆车都是一个单独的线程。

当赛车完成比赛时,每一辆车都会调用这个方法。我已经以不同的计时器速度测试了该方法,它似乎工作正常。但我确实意识到每个线程都在访问变量 carsComplete,有时是在完全相同的时间(至少在 date 命令给我的范围内)。

所以我的问题是:这个方法是线程安全的吗?

 public static String completeRace()
 {
      Date accessDate = new Date();
      System.out.println("Cars Complete: " + carsComplete + " Accessed at " + accessDate.toString());
      switch(++carsComplete)
      {
           case 1: return "1st";
           case 2: return "2nd";
           case 3: return "3rd";
           default: return carsComplete + "th";    
      }
 }

【问题讨论】:

    标签: java multithreading pre-increment


    【解决方案1】:

    问题是“预增量运算符线程安全吗?”

    Ans:不,为什么?因为涉及的指令数量。 Atomic 表示单个操作,这里需要执行加载/添加/存储操作。所以不是原子操作。

      Same for post increment.
    

    【讨论】:

      【解决方案2】:

      ++ 运算符不是原子的。看这里http://madbean.com/2003/mb2003-44/。 对于原子操作,您可以使用AtomicInteger

      AtomicInteger atomicInteger = new java.util.concurrent.atomic.AtomicInteger(0)
      

      每次你想增加你都可以调用atomicInteger.incrementAndGet() 方法,它返回一个原始的int。 0 是原子整数的默认初始值。

      【讨论】:

      • 我仍然不明白为什么原子操作是线程安全的?两个线程同时执行 CAS 会发生什么?这不是比赛条件吗??
      • @Hardik 没有竞争条件,因为 happens before(en.wikipedia.org/wiki/Happened-before) 关系由原子操作强制执行,因此产生了线程安全性。
      【解决方案3】:

      与 C++ 中的操作符 ++ 相同,它不是原子的。

      实际上,在后台执行的指令不止 1 条(不要被简单的 ++i 所迷惑;它是 load/add/store),因为在没有同步的情况下涉及超过 1 条指令,您可能有各种错误结果的交错。

      如果您需要以线程安全的方式增加 carsComplete,您可以使用 java 的构造 AtomicInteger 或者您可以同步整个方法

      【讨论】:

        【解决方案4】:

        int 的预增量不是线程安全的,请使用无锁的AtomicInteger

        AtomicInteger carsComplete = new AtomicInteger();
        
        //...
        
        switch(carsComplete.incrementAndGet())
        

        顺便说一句,下面的代码也不是线程安全的。你能说出原因吗?

        carsComplete.incrementAndGet();
        switch(carsComplete.get())
        

        【讨论】:

        • 因为线程 1 可以到达 switch 语句,所以让线程 2 运行 incrementAndGet 从而增加 carsComplete 的后备存储,此时线程 1 再次执行,后备存储已递增两次,一次按每个线程。
        • 没错,不过这次没有奖品 ;-)。
        【解决方案5】:

        不,您应该使用java.util.concurrent.atomic.AtomicInteger 之类的东西。看看它的getAndIncrement() 方法。

        【讨论】:

        • 或者在递增之前同步一些东西。
        • @Ted:我敢打赌同步某些东西会更贵。
        • @Eric - 如果 AtomicInteger 在某些时候(可能在本机代码中)没有进行同步,我会感到惊讶。进行基准测试会很有趣。从 AtomicInteger 的源代码来看,它似乎依赖于一种原子比较和设置算法,该算法会重试直到没有检测到问题。这没有最坏的情况。在高度线程化的场景中,我认为它在随机时间可能会非常慢。
        • @Ted Hopp 不过,CAS 循环应该比任何真正的锁都快(怎么会比简单的 CAS 更便宜?)。如果你需要一个公平的锁,那么在不太可能的情况下,这将花费更多的性能。
        • @Voo - 平均而言,我同意:CAS 循环往往很快。不过,我的观点是,在高度争议的环境中,任何特定线程都可能无限期地饿死。 (还有一个问题,不是每个硬件架构都支持原子 CAS。但是,我承认几乎所有运行 Java 的机器都支持。)
        猜你喜欢
        • 1970-01-01
        • 2010-12-15
        • 2015-12-17
        • 1970-01-01
        • 2022-08-17
        • 1970-01-01
        • 2012-05-20
        • 1970-01-01
        • 2012-10-03
        相关资源
        最近更新 更多