我的并发代码是同步的,但它没有同步打印值,但是每个线程都打印相同的值

问题描述

我正在尝试演示如何使用 synchronized 关键字修复 RaceCondition。下面的代码一个 ZooStock 对象的变量组成,该变量由 4 个线程递增和打印。我已经同步了方法 (addGrass()) 但是所有线程打印的值都是相同的,即

当前输出 1002g,1002g,1002g

预期输出 1001g,1003g,1004g

  public static void main(String[] args){
          ZooStockSync zooStockNew = new ZooStockSync(1000,750,5000);
          ExecutorService executorService = null;
          try{
              executorService = Executors.newFixedThreadPool(10); //Creating a Thread pool of size 10
              for(int i=0; i<4; i++){
                  executorService.submit(()->new ZooWorkerSync(zooStockNew).addGrass()); //
              }
          }finally{
              if(executorService != null) executorService.shutdown();
          }
      }

包含同步方法的类:

class ZooWorkerSync implements Runnable {

        ZooStockSync zooStock;

        ZooWorkerSync(ZooStockSync zooStock){
            this.zooStock = zooStock;
        }

        public synchronized void addGrass(){
            zooStock.grass++;
            System.out.print(zooStock.grass + "g ");
        }
}

但是,当我在传统 (java.lang.Thread) 意义上创建线程而不使用 java.util.concurrent 包中的 Executor 线程时。

public static void main(String[] args){
    ZooStockSync zooStockTraditional = new ZooStockSync(1000,5000);
    ZooWorkerSync[] workerThreads = new ZooWorkerSync[4]; //Set all elements in the array to be a ZooWorker object
    Arrays.fill(workerThreads,new ZooWorkerSync(zooStockTraditional));
    for (ZooWorkerSync workerThread : workerThreads) {
        new Thread(workerThread).start(); //Start the worker threads off (this invokes the run method in the ZooWorker class)
    }
  }

输出符合预期:1001g 5010w 751h 1002g 5020w 752h 1003g 5030w 753h 1004g 5040w 754h,注意 g 的按预期升序排列。 (忽略 h 和 w)

工作线程的run方法如下图所示:

@Override
public void run() {
    addGrass();
    addWater();
    addHay();
}

所以我的问题是,为什么 2 个输出不同,为什么我使用 java.util.concurent Executors 的线程打印出与传统方法相反的相同值?

解决方法

synchronized 锁定一个对象,并且因为您正在同步多个对象,所以它无法按您的意愿工作。

相反,你应该在一个公共对象上同步,比如类。

class ZooWorkerSync implements Runnable {

        ZooStockSync zooStock;

        ZooWorkerSync(ZooStockSync zooStock){
            this.zooStock = zooStock;
        }

        public void addGrass(){
            synchronized (ZooWorkerSync.class) {
              zooStock.grass++;
              System.out.print(zooStock.grass + "g ");
            }
        }
}
,

在您的 ExeutorService 示例中,您正在创建 ZooWorkerSync 类的多个实例,在 Thread 示例中您正在重用同一个实例。

在您的第一个示例中,synchronized 关键字实际上没有任何作用,因为它是一个实例级锁。您可以尝试手动同步课程。

,

我没有看到 ZooStockSync 的代码,但看起来您正在同步线程 (ZooWorkerSync) 上的一个方法,而不是正在共享的对象。然后您可以访问 ZooStockSync 中的一个字段:

 public synchronized void addGrass(){
        zooStock.grass++;
        System.out.print(zooStock.grass + "g ");
    }

但是对该字段(草)的访问可能不是线程安全的。每个 ZooWorkerSync 线程可以同时访问该字段。我建议将 synchronized 放在 ZooStockSync 中增加字段的方法上。例如:

public synchronized void incrementGrass() {
     grass++;
}

您还可以对草地使用 volatile 关键字,或将其设为 AtomicInteger

,

错误是由于我在 Executors 示例中创建了 ZooWorkerSync 的几个实例,如上所示使用 synchronized 一词作为实例级锁,因此它实际上是跨多个实例的冗余,它仅对单个实例,因此我需要修改我的 executorService 以仅从单个实例调用 addGrass():

  public static void main(String[] args){
          ZooStockSync zooStockNew = new ZooStockSync(1000,750,5000);
          ExecutorService executorService = null;
          try{
              executorService = Executors.newFixedThreadPool(10); //Creating a Thread pool of size 10
              ZooWorkerSync zooWorkerSync = new ZooWorkerSync(zooStockNew);
              for(int i=0; i<4; i++){
                  executorService.submit(zooWorkerSync::addGrass);
              }
          }finally{
              if(executorService != null) executorService.shutdown();
          }
  }