【问题标题】:java thread synchronized & lock no effect?java线程同步和锁定没有效果?
【发布时间】:2021-11-13 20:59:49
【问题描述】:

为什么我在同步或锁定方法中执行“i++”时会得到随机结果?

public class aaa implements Runnable {
    static int count = 0;
    public static void main(String[] args) {
        aaa aaa = new aaa();
        aaa.create();
    }
    public void create() {
        ExecutorService executor = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 1000; i++) {
            aaa thread = new aaa();
            executor.execute(thread);
        }
        executor.shutdown();
        while (true){
            if(executor.isTerminated()){
                System.out.println("a " + count);
           break;
            }
        }
    }
    @Override
    public void run() {
        this.test();
    }
    public void test() {
        Lock lock = new ReentrantLock();
        try {
            lock.lock();
            count++;
            System.out.println(count);
        } finally {
            lock.unlock();
        }
    }
}

或者:

    public  synchronized void test() {
            count++;
            System.out.println(count);
        }

结果是一个随机数,有时是 1000,有时是 998、999 ...等等,而“测试”方法内部的打印不是按顺序排列的,就像:

867
836
825
824
821
820
819
817
816
a 999

但是,如果它在同步块中,一切看起来都不错:

    public void test() {
        synchronized (aaa.class) {
            count++;
            System.out.println(count);
        }
    }

结果:

993
994
995
996
997
998
999
1000
a 1000

我认为上面所有的方法都应该给我相同的结果1000,并且自增应该是顺序的,但只有最后一个方法有效。代码有什么问题?请帮忙!!!

【问题讨论】:

    标签: java multithreading concurrency threadpool synchronized


    【解决方案1】:

    经验法则:在你想用它保护的变量之后的下一行声明你的锁变量,并用相同的关键字声明它。例如,

    public class aaa implements Runnable {
        static int count = 0;
        static Lock countLock = new ReentrantLock();
        ...
    

    如果您对此处的任何其他答案进行了足够深入的阅读,那么您就会明白为什么这会有所帮助。

    【讨论】:

      【解决方案2】:

      您必须创建一个互斥体,即

      static Lock lock = new ReentrantLock();
      

      您的同步方法不起作用,因为您正在创建 N aaa 实例然后,每个(非静态)方法都是不同的(具有自己的互斥锁)。

      您的synchronized (aaa.class) 有效,因为aaa.class 对于所有aaa 实例和方法都是相同的Object

      然后,如果您需要同步该方法,请确保它对于所有线程都是相同的,例如如果teststatic 对所有人都是一样的

      @Override
      public void run() {
          test();
      }
      
      public static synchronized void test() {
              count++;
      }
      

      但你可以注入一个“计数器类”,例如

      class Counter {
          int count = 0;
          // non static but synchronized for all (since they use the same `counter` object)
          synchronized void inc() {
              count++;
          }
      }
      

      用于所有线程

      ...
      SyncTest thread = new SyncTest(counter); // <== the same
      ...
      

      (完整代码)

      public class SyncTest implements Runnable {
          private final Counter c;
      
          public SyncTest(Counter c) {
              this.c = c;
          }
      
          static class Counter {
              int count = 0;
              // non static but synchronized for all (since they use the same `counter` object)
              synchronized void inc() {
                  count++;
              }
          }
      
          @Override
          public void run() {
              test();
          }
      
          public void test() {
                  this.c.inc();
          }
      
          public static void main(String[] args) {
      
              // one counter for all
              Counter counter = new Counter();
      
              ExecutorService executor = Executors.newFixedThreadPool(100);
              for (int i = 0; i < 10000; i++) {
                  SyncTest thread = new SyncTest(counter);
                  executor.execute(thread);
              }
              executor.shutdown();
              while (true) {
                  if (executor.isTerminated()) {
                      System.out.println("a " + counter.count);
                      break;
                  }
              }
          }
      
      }
      

      【讨论】:

      • 谢谢大佬,既然这种情况下同步方法不起作用,那么在什么情况下同步方法有效呢?可以举个例子吗?
      • @ee 有很多方法可以做到这一点,我已经用两个更新了答案
      【解决方案3】:

      您正在创建 aaa 的多个实例,每个实例都创建自己的 ReentrantLock,并且每个执行中的线程都顺利地从自己的实例中获取锁。

      public void test() {
              Lock lock = new ReentrantLock();
              try {
                  lock.lock();
                  count++;
                  System.out.println(count);
              } finally {
                  lock.unlock();
              }
          }
      

      由于 aaa 有多个实例,每个线程都在自己的实例上运行,同步方法使用 aaa.class 的当前对象

      public  synchronized void test() {
              count++;
              System.out.println(count);
          }
      

      在这种方法中获得正确结果的原因是,您使用 aaa.class 作为同步对象

      public void test() {
          synchronized (aaa.class) {
              count++;
              System.out.println(count);
          }
      }
      

      解决方案是,在所有线程中重用相同的锁(ReentrantLock)。将锁定义在与变量计数相同的级别将解决问题。

      【讨论】:

        猜你喜欢
        • 2011-10-12
        • 1970-01-01
        • 1970-01-01
        • 2016-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多