Home » Java » Is 'synchronized' really just syntactic sugar?

Is 'synchronized' really just syntactic sugar?

Posted by: admin December 28, 2021 Leave a comment

Questions:

I am new to multithreading, and I wrote this code which prints the numbers 1-10000 by having concurrently running threads increment and print a variable.

Here’s the code I’m using:

package threadtest;

public class Main{

    static int i=0;
    static Object lock=new Object();

    private static class Incrementer extends Thread{

        @Override
        public void run(){
            while (true){
                synchronized(lock){
                        if (i>=10000)
                            break;
                        i++;
                        System.out.println(i);
                }
            }               
        }
    }


    public static void main(String[] args) {
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
    }
}

This works – I wrote up a test program to check the output, and the numbers printed are exactly 1-10000 in order.

My question is this: I’ve heard that synchronized is only syntactic sugar. But I can’t seem to achieve a successful result without using it. What am I missing?

Answers:

synchronized is by no means syntactic sugar for anything. There is no way to work locks in Java without using the synchronized keyword.

Where there is “syntactic sugar” of a sort in locks in Java is that synchronized can apply both to blocks (as you’ve done it above) and to whole methods. The following two methods are roughly equivalent in semantics:

synchronized void method1() {
  // ... do stuff ...
}

void method2() {
  synchronized(this) {
    // ... do stuff ...
  }
}

So why would you want to do the second version instead of the first?

  • Synchronized method invocations are far slower than plain old method invocations, like by about an order of magnitude. If your synchronized code isn’t guaranteed to always execute (say it’s in a conditional), then you probably don’t want to synchronize the whole method.
  • Synchronized methods hold locks for longer than synchronized blocks (because of all the method setup/tear down code). The second method above will hold the lock for less time because the time spent setting up and tearing down the stack frame won’t be locked.
  • You can have much finer control over exactly what you’re locking if you go with the synchronized blocks.
  • (Courtesy of starblue) Synchronized blocks can use objects other than this for locking which gives you more flexible locking semantics.

###

It sounds like your sources are just wrong. The syncrhonized keyword is important to use – and use properly – when writing thread-safe code. And it sounds like your own experiments bear this out.

For more on synchronization in Java:

Java Synchronized Methods

Java Locks and Synchronized Statements

###

Actually as of Java 5 you (formally) have an alternative set of tools in java.util.concurrent. See here for more details. As detailed in the article the monitor locking model provided at Java’s language level has a number of significant limitations and can be difficult to reason about when there are a complex set of interdependent objects and locking relationships making live-lock a real possibility. The java.util.concurrent library offers locking semantics which might be more familiar to programmers who’ve had experience in POSIX-like systems

###

Of course, “synchronized” is just syntactic sugar – extremley useful syntactic sugar.

If you want sugar-free java programs, you should be writing directly in java byte code the monitorenter, monitorexit, lock, and unlock operations referenced in VM Specifications 8.13 Locks and Synchronization

There is a lock associated with every object. The Java programming
language does not provide a way to
perform separate lock and unlock
operations; instead, they are
implicitly performed by high-level
constructs that always arrange to pair
such operations correctly. (The Java
virtual machine, however, provides
separate monitorenter and monitorexit
instructions that implement the lock
and unlock operations.)

The synchronized statement computes a
reference to an object; it then
attempts to perform a lock operation
on that object and does not proceed
further until the lock operation has
successfully completed. (A lock
operation may be delayed because the
rules about locks can prevent the main
memory from participating until some
other thread is ready to perform one
or more unlock operations.) After the
lock operation has been performed, the
body of the synchronized statement is
executed. Normally, a compiler for the
Java programming language ensures that
the lock operation implemented by a
monitorenter instruction executed
prior to the execution of the body of
the synchronized statement is matched
by an unlock operation implemented by
a monitorexit instruction whenever the
synchronized statement completes,
whether completion is normal or
abrupt.

A synchronized method automatically
performs a lock operation when it is
invoked; its body is not executed
until the lock operation has
successfully completed. If the method
is an instance method, it locks the
lock associated with the instance for
which it was invoked (that is, the
object that will be known as this
during execution of the method’s
body). If the method is static, it
locks the lock associated with the
Class object that represents the class
in which the method is defined. If
execution of the method’s body is ever
completed, either normally or
abruptly, an unlock operation is
automatically performed on that same
lock.

Best practice is that if a variable is
ever to be assigned by one thread and
used or assigned by another, then all
accesses to that variable should be
enclosed in synchronized methods or
synchronized statements.

Although a compiler for the Java
programming language normally
guarantees structured use of locks
(see Section 7.14, “Synchronization”),
there is no assurance that all code
submitted to the Java virtual machine
will obey this property.
Implementations of the Java virtual
machine are permitted but not required
to enforce both of the following two
rules guaranteeing structured locking.

Let T be a thread and L be a lock.
Then:

  1. The number of lock operations performed by T on L during a method
    invocation must equal the number of
    unlock operations performed by T on L
    during the method invocation whether
    the method invocation completes
    normally or abruptly.

  2. At no point during a method invocation may the number of unlock
    operations performed by T on L since
    the method invocation exceed the
    number of lock operations performed by
    T on L since the method invocation.

In less formal terms, during a method
invocation every unlock operation on L
must match some preceding lock
operation on L.

Note that the locking and unlocking
automatically performed by the Java
virtual machine when invoking a
synchronized method are considered to
occur during the calling method’s
invocation.

###

Synchronization is one of the most important concepts while programming in multi thread environment.
While using synchronization one has to consider the object over which synchronization takes place.
For example if a static method is to be synchronized then the synchronization must be on the class level using

synchronized(MyClass.class){
 //code to be executed in the static context
}

if the block in instance method is to be synchronized then the synchronization must be using an instance of an object which is shared between all the threads.
Most poeple go wrong with the second point as it appears in your code where the synchronization appears to be on different objects rather than a single object.