java:可以同步块交错吗?

时间:2018-05-15 17:30:18

标签: java concurrency

我有一个意外(至少对我来说)输出此代码

public class Test {
    static boolean condition = false;

    void runme() {
        var reader = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println("waiting for condition");
                    while (!condition) {}
                    System.out.println("condition is true");
                }
            }
        };

        var writer = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println("condition is set to true");
                    condition = true;
                }
            }
        };

        new Thread(reader).start();
        new Thread(writer).start();

    }

    public static void main(String[] args) {
        new Test().runme();
    }
}

根据文档,如果reader对象首先启动,我预计会出现死锁,因为

  1. 获取this(进入同步块)
  2. 的锁定
  3. 打印“等待条件”
  4. 永远陷入无限循环
  5. 另一个线程等待this锁,进入自己的同步块
  6. 但是,在某些代码运行中,我得到了输出

    waiting for condition
    condition is set to true
    condition is true
    

    我是否遗漏了某些内容或者我误解了同步块/方法的工作原理?

3 个答案:

答案 0 :(得分:3)

两个synchronized (this)语句引用Runnable个匿名类 因此,两个Runnable实例的同步不会在同一个锁上运行 您必须在外部类实例上进行同步才能锁定同一监视器,例如:

synchronized (Test.this) {...}

此外,请注意,通过使用lambda来实现Runnable功能接口,例如:

var writer = () -> {
    synchronized (this) {
        System.out.println("condition is set to true");
        condition = true;
    }
};

您可以将实际语法(synchronized (this))保留为this,在这种情况下,不会引用不存在但引用外部实例的匿名类。

答案 1 :(得分:2)

在您的代码中,synchronized(this)指的是两个不同的对象。因此,两个代码都不会阻塞另一个,它们只是同时运行。

另一种看待它的方法是不使用var或本地类。只需声明两个与readerwriter完全相同的顶级类:

//       var reader = new Runnable() {
class Reader implements Runnable {
        @Override
        public void run() {
            synchronized (this) {
                System.out.println("waiting for condition");
                while (!condition) {}
                System.out.println("condition is true");
            }
        }
    }


//        var writer = new Runnable() {
class Writer implements Runnable {
        @Override
        public void run() {
            synchronized (this) {
                System.out.println("condition is set to true");
                condition = true;
            }
        }
    }

很明显,this在这种情况下是指每个类的实例,而不是同一个对象,因此这两个类永远不会相互阻塞。

答案 2 :(得分:2)

引用this引用当前对象。在内部类this的实例方法内部引用内部类的当前对象。如果你想访问外部封闭类的当前对象,那么你需要使用:OuterClassName.this

在您的情况下,您有两个具有匿名类的单独对象。同步块不共享要锁定的公共对象,所以实际上没有同步,而是两个线程独立并行运行。