【问题标题】:How does thread interference actually happens in the `Counter` example class?在 `Counter` 示例类中,线程干扰实际上是如何发生的?
【发布时间】:2025-12-25 05:25:09
【问题描述】:

我正在尝试学习线程干扰的概念,并在Java教程Oracle中遇到了以下示例:

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

Oracle 教程提到如果有两个线程尝试访问变量c,可能会导致线程干扰,其中一个线程所做的更改不会被另一个线程看到。

但是,它没有提供任何代码来实际解释这个概念。有人可以提供一个基于Counter 类的代码示例来演示线程干扰是如何实际发生的吗?

【问题讨论】:

    标签: java multithreading data-consistency


    【解决方案1】:

    创建多个线程并从这些线程调用increment(), decrement()value()

    示例代码如下:

    class Counter {
        private int c = 0;
    
        public void increment() {
            c++;
        }
    
        public void decrement() {
            c--;
        }
    
        public int value() {
            return c;
        }
    
        public static void main(String args[]){
            Counter c = new Counter();
            for ( int i=0; i<3; i++){
                Thread t = new Thread(new MyRunnable(c));
                t.start();
            }
        }
    }
    class MyRunnable implements Runnable{
        Counter counter;
        public MyRunnable(Counter c){
            counter = c;
        }
        public void run(){
            counter.increment();
            System.out.println("Counter value after increment:"+counter.value()+" from thread:"+  Thread.currentThread().getName());
            counter.decrement();
            System.out.println("Counter value after decrement:"+counter.value()+" from thread:"+  Thread.currentThread().getName());
        }
    }
    

    输出:(每次运行的输出会有所不同)

    Counter value after increment:1 from thread:Thread-0
    Counter value after decrement:2 from thread:Thread-0
    Counter value after increment:2 from thread:Thread-2
    Counter value after decrement:1 from thread:Thread-2
    Counter value after increment:3 from thread:Thread-1
    Counter value after decrement:0 from thread:Thread-1
    

    现在从输出中,您可以了解线程干扰。要解决此问题,您可以使用AtomicInteger

    请查看以下帖子了解更多详情:

    Why is synchronized not working properly?

    【讨论】:

      【解决方案2】:

      只需多次运行代码,其中一次幸运的时候,您将能够看到线程干扰在起作用。

      在这种情况下很少观察到,因为它是一个小程序,发生的事情不多。如果你制作多个递增和递减线程,线程干扰将更容易观察。

      class Counter {
      private int c = 0;
      
      public void increment() {c++;}
      
      public void decrement() {c--;}
      
      public int value() {
          return c;
      }
      
      public static void main(String[] args) {
      
          Counter x = new Counter();
          Runnable r1 = new Runnable() {
              @Override
              public void run() {
                  x.increment();
              }
          };
      
          Runnable r2 = new Runnable() {
              @Override
              public void run() {
                  x.decrement();
              }
          };
      
          Thread t1 = new Thread(r1);
          Thread t2 = new Thread(r2);     
          t1.start();
          t2.start();
          System.out.println(x.c);
      
      }
      
      }
      

      编辑:我决定添加多线程案例,我无法抗拒

      编辑 2 :这是第二次编辑。多线程案例因为这会产生超出此问题范围的问题,所以我决定将其删除。以前我正在制作一个线程数组并运行它们。相反它会更好显示一个执行大量增量的线程和另一个执行大量减量的线程。

      我使用了 Thread.Sleep() 导致主线程进入睡眠状态,这将确保在两个线程完成操作后打印 c。

      class Counter {
      private int c = 0;
      
      public void increment() {
          for (int i = 0; i < 10000; i++) {
              c++;
          }
      
          }
      
      public void decrement() {
          for (int i = 0; i < 5000; i++) {
              c--;
          }
          }
      
      public int value() {
      return c;
      }
      
      public static void main(String[] args) {
      
      Counter x = new Counter();
      Runnable r1 = new Runnable() {
          @Override
          public void run() {
              x.increment();
          }
      };
      
      Runnable r2 = new Runnable() {
          @Override
          public void run() {
              x.decrement();
          }
      };
      
      Thread t1 = new Thread(r1);
      Thread t2 = new Thread(r2);     
      t1.start();
      t2.start();
      try {
          Thread.sleep(2000);
      } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
      }
      if(!(t1.isAlive() && t2.isAlive()))
      System.out.println(x.c);//expected answer 5000
      }
      }
      

      注意:同步增量/减量方法给出正确答案。自己尝试。

      【讨论】:

        【解决方案3】:

        比起代码,我更喜欢解释会发生什么。假设 2 个线程,A 和 B 正在访问 同一个计数器对象A 调用 increment 并且 B 调用 递减。这些操作中的任何一个都至少包含 3 个步骤。

        1. 从内存中读取C
        2. 递增或递减C
        3. C写回内存。

        当 A 和 B 尝试同时递增和递减时,一个线程可能会从内存中读取 C(步骤 1),而另一线程处于 步骤 2。在这种情况下,如果 c 的初始值为 5,首先线程 A 读取并将其递增到 6。然后线程 B 读取并将其递减到 4。请记住,B 在 A 完成写入之前进行这些更改c 回到记忆中。由于步骤是重叠的,一个线程所做的更改对另一个线程不可见,导致 c 的最终值为 6 或 4。但实际上我们的预期是 5

        这是两个线程相互干扰的示例。为了避免这种情况,我们使用线程同步。

        【讨论】: