简体   繁体   中英

java: can synchronized block interleave?

I have an unexpected (for me at least) output with this code

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();
    }
}

Based on the documentation, I expected a deadlock if the reader object starts first, since

  1. it acquires the lock for this (entering the synchronized block)
  2. prints "waiting for condition"
  3. get stuck in the infinite loop forever
  4. the other thread waits for the this lock, to get into its own synchronized block

However, on some runs of the code I get the output

waiting for condition
condition is set to true
condition is true

Am I missing something or have I misunderstood how synchronized blocks/methods work?

The two synchronized (this) statements reference the Runnable anonymous classes.
So the synchronizations of the two Runnable instances don't operate on the same lock.
You have to synchronize on the outer class instance to lock on the same monitor such as :

synchronized (Test.this) {...}

Additionally, note that by using a lambda to implement the Runnable functional interface such as :

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

you could keep the actual syntax ( synchronized (this) ) as this in this case doesn't refer the anonymous class that doesn't exist but refers the outer instance.

In your code, synchronized(this) refers to two different objects. So neither code blocks the other, they just run simultaneously.

Another way to look at it is to not use var or local classes. Just declare two top level classes that do the same thing as reader and writer :

//       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;
            }
        }
    }

It should be pretty obvious that this in this case refers to an instance of each class, not the same object, so these two classes could never block each other.

The reference this refers to the current object. Inside the instance method of an inner class this refers to the current object of the inner class. In case you want to access the current object of outer enclosing class then you need to use like : OuterClassName.this .

In your case you two separate objects of have anonymous classes. The synchronized blocks do not share common object to lock upon so actually there is no synchronization rather both the threads run in parallel independently.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM