javamultithreadingjava-threads

Synchronized Statements in Java Thread


I'm reading the Thread Topic on the official tutorial and I'm trying to understand the Intrinsic Look.

I understand the different basic between synchronized method, synchronized statement and synchronized static method now.

In the synchronized statement section in that article, given a example and they said

In this example, the addName method needs to synchronize changes to lastName and nameCount, but also needs to avoid synchronizing invocations of other objects' methods. (Invoking other objects' methods from synchronized code can create problems that are described in the section on Liveness.) Without synchronized statements, there would have to be a separate, unsynchronized method for the sole purpose of invoking nameList.add.

I really don't understand what they said. Please, explain it to me with a simple example.

Then, they said:

Synchronized statements are also useful for improving concurrency with fine-grained synchronization. Suppose, for example, class MsLunch has two instance fields, c1 and c2, that are never used together. All updates of these fields must be synchronized, but there's no reason to prevent an update of c1 from being interleaved with an update of c2 — and doing so reduces concurrency by creating unnecessary blocking. Instead of using synchronized methods or otherwise using the lock associated with this, we create two objects solely to provide locks.

with example code

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

How should I understand it? I think, it simply mention there are two object were created to manage the respective variable c1, c2, anything else?

Update

After reading all comments and the answers of @tgdavies and @michael, I'm extremely grateful for your support to help me to understand these concepts clearly.

For second quotes, I try to make a comparison to understand it. synchronized statement actually is faster than synchronized method

  1. MsLunch.java with synchronized statements

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }

    public long getC1() {
        return c1;
    }

    public long getC2() {
        return c2;
    }
}

  1. MsLunch2.java with synchronized method
public class MsLunch2 {
    private long c1 = 0;
    private long c2 = 0;

    public synchronized void inc1() {
        c1++;
    }

    public synchronized void inc2() {
        c2++;
    }

    public long getC1() {
        return c1;
    }

    public long getC2() {
        return c2;
    }
}
  1. Test.java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        // synchronized statements
//        MsLunch ms = new MsLunch();
        // synchronized method
        MsLunch2 ms = new MsLunch2();

        long start = System.currentTimeMillis();
        System.out.println(start);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                ms.inc1();
            }
            System.out.println(ms.getC1());
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                ms.inc2();
            }
            System.out.println(ms.getC2());
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        long end = System.currentTimeMillis();
        System.out.println(end);
        System.out.println("Time = " + (end - start)/1000.0);
    }
}


Solution

  • The sample code from your first question is:

    public void addName(String name) {
        synchronized(this) {
            lastName = name;
            nameCount++;
        }
        nameList.add(name);
    }
    

    It's important that we don't call the add method of the nameList object while holding the lock on this because we don't know the behaviour of add -- maybe it takes a long time, for instance.

    Without synchronised statements (i.e. if we only had synchronised methods), we would need to write:

    public void addName(String name) {
        doSetLastName(name);
        nameList.add(name);
    }
    
    private synchronized doSetLastName(String name) {
            lastName = name;
            nameCount++;
    }
    

    Explanation of the phrase:

    avoid synchronizing invocations of other objects' methods

    To 'avoid' doing something means not to do it, so we should "not synchronize invocations of other objects' methods"

    An "invocation" is a call, so in the example above, we invoke the add method on the nameList object.

    Code in instance methods is run in the context of a particular object, that is, the object that this refers to, so "other objects' methods" are methods on objects which are not the this object. In the example above add is a method on the object nameList.

    So we can rephrase it as "Don't call methods on other objects while holding locks" (unless of course you decide that you need to for thread safety.)

    For your second question, imagine that MsLunch was implemented like this:

    public class MsLunch {
        private long c1 = 0;
        private long c2 = 0;
    
        public synchronized void inc1() {
                c1++;
        }
    
        public synchronized void inc2() {
                c2++;
        }
    }
    

    This is still perfectly thread safe, but because both synchronised sections lock in the MsLunch instance a thread calling inc1 will be blocked by a thread calling inc2. That's unnecessary to preserve thread safety.