一段java并发同步示例代码的疑惑

 尊园2010_630 发布于 2022-11-06 16:37
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class CachedThreadPool {

    private static int id = 0;

    public static void main(String[] args) {
        new CachedThreadPool().fun();
    }

    private void fun() {
        ExecutorService exe = Executors.newCachedThreadPool();
        ArrayList> list = new ArrayList>();
        for (int i=0;i<3;i++) {
            list.add(exe.submit(new TaskCall()));
        }

        for (Future fs : list) {
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 
        }
        exe.shutdown();
    }

    private synchronized String getId() {
        return ++id + "";
    }

    class TaskCall implements Callable {

        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            return getId();
        }

    }
}

这段代码并没有什么问题,但是为何把getId()函数放到TaskCall内部,却会得到同步失败的输出(2 2 3或者3 3 3等),这是为什么呢?

2 个回答
  • 按你的测试代码,楼上回答能解释。

    然而:

    测试代码有错误,以下是正确的测试代码:

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.*;
    
    
    public class CachedThreadPool {
    
      private static int id = 0;
    
      private static List<Integer> results = Collections.synchronizedList(new ArrayList<>());
    
      public static void main(String[] args) throws Exception {
        new CachedThreadPool().fun();
      }
    
      private void fun() throws Exception {
        ExecutorService exe = Executors.newCachedThreadPool();
        List<Future<String>> list = new ArrayList<>();
        for (int i=0;i<100;i++) {
          list.add(exe.submit(new TaskCall()));
        }
        exe.shutdown();
        for (Future<String> fs : list) {
          fs.get();
        }
        // 断言
        for (int i = 1; i < results.size(); i++) {
          if (results.get(i) != results.get(i-1) + 1) {
            throw new IllegalStateException();
          }
        }
        System.out.println("\n" + results);
      }
    
      private synchronized String getId() {
          ++id;
          System.out.print(id + ", ");
          results.add(id);
          return id + "";
      }
    
      class TaskCall implements Callable<String> {
    
        @Override
        public String call() throws Exception {
          return getId();
        }
    
      }
    }
    

    然后是分析:

    先翻译几处代码,以便理解:
    1.

    private synchronized String getId() {
      return ++id + "";
    }
    

    等价于

    private String getId() {
      synchronized(this) {
        return ++id + "";
      }
    }
    

    2.

    // TaskCall
    return getId();
    

    等价于

    // TaskCall
    return CachedThreadPool.this.getId();
    

    可以看到同步范围仅限于this,也就是CachedThreadPool的实例,只有一个。但是static字段是类的字段,不是实例的字段,因此不在加锁范围!然而,同步会刷新代码块内所有用到的变量,不论static与否。而唯一实例使++代码块被单线程独占。两者结合,意外地做到了并发安全。

    还可以试验一下,synchronized改成synchronized(CachedThreadPool.class) {...},并把main标为synchronized,会导致死锁。

    synchronized的知识:指定了一个同步范围,进出范围时会刷新相关变量,阻止其他线程进入该范围。synchronized method的范围是this,synchronized static method的范围是class。

    补充:如果同一个类有的方法写了synchronized,有的方法没写,也达不到同步。

    2022-11-12 01:56 回答
  • 因为放到TaskCall里之后,synchronized表示在一个TaskCall实例上同步执行。有3个实例,它们之间是不同步的。
    而放在外面是在一个CachedThreadPool中同步。

    2022-11-12 01:56 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有