【问题标题】:Increments and decrements in multi threaded environment多线程环境中的增量和减量
【发布时间】:2018-06-11 11:08:27
【问题描述】:

我正在尝试在多线程环境中对 int 变量进行经典的递增/递减。这是我的示例代码。

public class SyncIncDec {


    public static void main(String[] args) {

        SyncCounter count = new SyncCounter();

        Thread incThread = new Thread(() -> {
            count.increment();
        });

        Thread decThread = new Thread(() -> {
            count.decrement();
        });

        Thread displayThread = new Thread(() -> {
            System.out.println("Count value : " + count.getX());
        });

        incThread.start();
        decThread.start();
        displayThread.start();      

        try {
            incThread.join();
        } catch (InterruptedException e) {
//          e.printStackTrace();
        }

        try {
            decThread.join();
        } catch (InterruptedException e) {
//          e.printStackTrace();
        }

        try {
            displayThread.join();
        } catch (InterruptedException e) {
//          e.printStackTrace();
        }

    }

}


class SyncCounter {

    private int x=0;

    public SyncCounter() {
        super();
    }

    public SyncCounter(int y) {
        super();
        x = y ;
    }

    synchronized int  getX() {
        return x; 
    }

    void setX(int y) {
        x = y ;
    }

    void increment() {
        ++x;
    }


    void decrement() {
        --x;
    }

}

虽然我对所有三个线程都使用了 join() 方法,但我仍然得到不一致的结果。 这里加入不是意味着主线程要等到每个线程完成执行吗?我什至尝试将同步添加到三个方法签名中的每一个;但我得到不一致的结果。

除了使用 Atomic 版本的变量,我还能如何确保我总是得到 0?

【问题讨论】:

  • 为什么你不想使用 AtomicInteger ?
  • @ValentinMichalak 这些只是为了我的锻炼目的。除了使用 AtomicInteger 之外,尝试所有可能的解决方案。
  • 练习提示:永远不要有空的 catch 块。

标签: java multithreading concurrency


【解决方案1】:

只有在所有线程都启动后,才能在三个线程上调用join()。因此,您无法保证 displayThread 变量引用的线程在递增和递减计数器的线程之后运行。
为确保这一点,请在启动这些线程后调用 join()

incThread.start();
decThread.start();
incThread.join();
decThread.join();
displayThread.start(); 

它将阻塞当前线程,直到执行递增和递减,以及在这些线程的start() 调用之后调用join() 的任何顺序。

【讨论】:

    【解决方案2】:

    您的 SyncCounter 根本不是线程安全的。可变方法的增量和减量应该同步。现在实现这样一个类的正确方法是在atomic orations。 例如:

    class SyncCounter {
    
        private final AtomicInteger x;
    
        public SyncCounter() {
         this(0);   
        }
    
        public SyncCounter(int x) {
           this.x = new AtomicInteger(x);
        }
    
        int getX() {
            return x.get(); 
        }
    
        void setX(int x) {
            this.x.set(x);
        }
    
        int increment() {
            return x.incrementAndGet();
        }
    
    
        int decrement() {
            return x.decrementAndGet();
        }
    
    }
    

    以及测试代码:

        final Thread incThread = new Thread(() -> {
            count.increment();
        });
    
        final Thread decThread = new Thread(() -> {
            count.decrement();
        });
    
        Thread displayThread = new Thread(() -> {
            incThread.join();
            decThread.join();
            System.out.println("Count value : " + count.getX());
        });
    

    【讨论】:

    • 我在所有三个方法签名中都添加了同步关键字;但是我得到了不一致的结果。使用 AtomicInteger 是解决此问题的唯一方法吗?
    【解决方案3】:

    虽然我对所有三个线程都使用了 join() 方法,但我仍然得到不一致的结果。这里join不是意味着主线程要等到每个线程都执行完吗?

    您的代码中有 2 个问题。

    • 在您的SyncCounter 类中,仅同步getX() 方法。因为您有 3 个线程共享同一个此类的实例,所以读取或更新共享字段的任何方法都需要为 synchronized。这意味着increment()decrement() 方法也需要为synchronized。正如@Victor 所述,将SyncCounter 替换为AtomicInteger 是一个简单的解决方案,尽管我怀疑您的练习需要手动完成。

      ...
      synchronized int increment() {
      ...
      synchronized int decrement() {
      
    • 在您的线程模型中,递增和递减线程与显示线程之间存在竞争条件。仅仅因为您在其他线程之后启动显示线程并不意味着它最后运行。可能是显示线程首先完成,在这种情况下它将打印 0 或者它可以打印 -1 或 1,具体取决于竞争条件。这里最简单的解决方案是让主线程加入增量和减量线程,然后然后打印出结果。

      // start the inc and dec threads running in the background
      incThread.start();
      decThread.start();
      // wait for inc thread to finish
      incThread.join();
      // wait for dec thread to finish
      decThread.join();
      // now we can print out the value of the counter
      System.out.println("Count value : " + count.getX());
      

      如果你必须有一个显示线程,那么它应该在之后开始 递增和递减线程的连接,如@davidxxx 推荐。

      // start the inc and dec threads running in the background
      incThread.start();
      decThread.start();
      // wait for inc thread to finish
      incThread.join();
      // wait for dec thread to finish
      decThread.join();
      // now start the display thread now that the increment/decrement is done
      displayThread.start();
      // wait for the display thread to finish
      displayThread.join();
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多