一段java并发同步示例代码的疑惑
PHPz
PHPz 2017-04-17 13:38:29
0
2
266
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等),这是为什么呢?

PHPz
PHPz

学习是最好的投资!

reply all (2)
迷茫

因为放到TaskCall里之后,synchronized表示在一个TaskCall实例上同步执行。有3个实例,它们之间是不同步的。
而放在外面是在一个CachedThreadPool中同步。

    阿神

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

    然而:

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

    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 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> list = new ArrayList<>(); for (int i=0;i<100;i++) { list.add(exe.submit(new TaskCall())); } exe.shutdown(); for (Future 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 { @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,有的方法没写,也达不到同步。

      Latest Downloads
      More>
      Web Effects
      Website Source Code
      Website Materials
      Front End Template
      About us Disclaimer Sitemap
      php.cn:Public welfare online PHP training,Help PHP learners grow quickly!