【问题标题】:Is there a race condition in this code?此代码中是否存在竞争条件?
【发布时间】:2017-11-21 12:59:02
【问题描述】:

我需要在 5 个线程中增加一个计数器,直到达到 500。像这样的 5 个线程正在工作。但我需要知道它是否有竞争条件。

此代码的另一个问题是它以错误的顺序给我打印。

class HiloContador extends Thread {

    static int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synchronized (new Integer(count)) {
                ++count;
                System.out.println(count);
            }
        }
    }
}

输出

4 6 8 9 10 11 12 5 14 15 3 17 18

一些想法?

【问题讨论】:

  • synchronized (new Integer(count)) { 完全是垃圾。我不知道你是怎么想出这个主意的,但这绝对是错误的。在很多方面也是如此。
  • 也许看看 AtomicInteger
  • 但是,如果我将 count 声明为 Integer 并将其用于 de synchronized,则输出仍然错误。
  • 如果你得到两次相同的值,输出是错误的,因为竞争条件
  • 那是因为Integer 是不可变的,所以你要每次都创建一个新的,而不是synchronize 在同一个对象上。就像@Stephan 说的,看看AtomicInteger

标签: java multithreading thread-safety synchronized


【解决方案1】:

是的,存在竞争条件,因为您实际上是在不同对象上同步代码。 同步块代表一个临界区。要进入临界区,您必须获得全局锁。您可以将对象用于锁定或整数。 这就是我的实现方式:

class HiloContador extends Thread {

    static int count = 0;
    static Object lock = new Object();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synchronized (lock) {
                ++count;
                System.out.println(count);
            }
        }
    }
}

现在 println 将按预期顺序排列。

【讨论】:

    【解决方案2】:

    分析

    此代码中是否存在竞争条件?

    是的,存在竞态条件:此行表示不正确的同步:

    synchronized (new Integer(count)) {
    

    这就是原因。
    让我们参考文档:

    每个对象都有一个与之关联的内在锁。按照惯例,需要对对象字段进行排他和一致访问的线程必须在访问对象之前获取对象的内在锁,然后在完成访问时释放内在锁。在获得锁和释放锁之间,线程被称为拥有内在锁。只要一个线程拥有一个内在锁,其他线程就不能获得相同的锁。其他线程在尝试获取锁时会阻塞。

    ——Intrinsic Locks and Synchronization (The Java™ Tutorials > Essential Classes > Concurrency).

    在当前情况下,第一句话很关键。当前的实现使用新对象的内在锁来同步每个循环迭代。这是不正确的。

    解决方案概述

    实现自增操作原子性的替代方案:

    1. 使用内部锁(synchronized 关键字)。请参阅Intrinsic Locks and Synchronization (The Java™ Tutorials > Essential Classes > Concurrency) 文章。
    2. 使用原子变量(java.util.concurrent.atomic 包:AtomicBooleanAtomicIntegerAtomicLong 等类)。请参阅Atomic Variables (The Java™ Tutorials > Essential Classes > Concurrency) 文章。
    3. 使用锁对象(java.util.concurrent.locks 包的类)。请参阅Lock Objects (The Java™ Tutorials > Essential Classes > Concurrency) 文章。

    最小解决方案

    让我们使用原子变量(解决方案#3),即AtomicInteger 类:它内置了所需的功能。

    另外,我们不要扩展 Thread 类:而是提取适当的 Runnable 接口实现。

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Program {
        public static void main(String[] args) throws InterruptedException {
            final CountingRunnable countingRunnable = new CountingRunnable();
            final List<Thread> threads = new ArrayList<>();
            for (int i = 0; i < 5; ++i) {
                final Thread thread = new Thread(countingRunnable);
                threads.add(thread);
                thread.start();
            }
    
            for (final Thread thread : threads) {
                thread.join();
            }
        }
    
        private static final class CountingRunnable implements Runnable {
            private final AtomicInteger count = new AtomicInteger(0);
    
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(count.incrementAndGet());
                }
            }
        }
    }
    

    其他参考

    1. 整个Lesson: Concurrency (The Java™ Tutorials > Essential Classes)
    2. Memory Consistency Errors (The Java™ Tutorials > Essential Classes > Concurrency)。了解happens-before关系。

    【讨论】:

      猜你喜欢
      • 2018-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-29
      • 2020-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多