Serialization and @Synchronized of lombok

@Synchronized

synchronized done right: Don't expose your locks.

Overview

@Synchronized is a safer variant of the synchronized method modifier. Like synchronized, the annotation can be used on static and instance methods only. It operates similarly to the synchronized keyword, but it locks on different objects. The keyword locks on this, but the annotation locks on a field named $lock, which is private. If the field does not exist, it is created for you. If you annotate a static method, the annotation locks on a static field named $LOCK instead.

If you want, you can create these locks yourself. The $lock and $LOCK fields will of course not be generated if you already created them yourself. You can also choose to lock on another field, by specifying it as parameter to the @Synchronized annotation. In this usage variant, the fields will not be created automatically, and you must explicitly create them yourself, or an error will be emitted.

Locking on this or your own class object can have unfortunate side-effects, as other code not under your control can lock on these objects as well, which can cause race conditions and other nasty threading-related bugs.

With Lombok

import lombok.Synchronized;

public class SynchronizedExample {
  private final Object readLock = new Object();
  
  @Synchronized
  public static void hello() {
    System.out.println("world");
  }
  
  @Synchronized
  public int answerToLife() {
    return 42;
  }
  
  @Synchronized("readLock")
  public void foo() {
    System.out.println("bar");
  }
}

Vanilla Java

public class SynchronizedExample {
  private static final Object $LOCK = new Object[0];
  private final Object $lock = new Object[0];
  private final Object readLock = new Object();
  
  public static void hello() {
    synchronized($LOCK) {
      System.out.println("world");
    }
  }
  
  public int answerToLife() {
    synchronized($lock) {
      return 42;
    }
  }
  
  public void foo() {
    synchronized(readLock) {
      System.out.println("bar");
    }
  }
}

Small print

If $lock and/or $LOCK are auto-generated, the fields are initialized with an empty Object[] array, and not just a new Object() as most snippets showing this pattern in action use. Lombok does this because a new object is NOT serializable, but 0-size array is. Therefore, using @Synchronized will not prevent your object from being serialized.

Having at least one @Synchronized method in your class means there will be a lock field, but if you later remove all such methods, there will no longer be a lock field. That means your predetermined serialVersionUID changes. We suggest you always add a serialVersionUID to your classes if you intend to store them long-term via java's serialization mechanism. If you do so, removing all @Synchronized annotations from your method will not break serialization.

If you'd like to know why a field is not automatically generated when you choose your own name for the lock object: Because otherwise making a typo in the field name will result in a very hard to find bug!

Explain

Serialization allows to convert an object to a stream, for sending that object over the network OR Save to file OR save into DB for letter usage.

There are some rules for serialization.

  • An object is serializable only if its class or its superclass implements the Serializable interface

  • An object is serializable (itself implements the Serializable interface) even if its superclass is not. However, the first superclass in the hierarchy of the serializable class, that does not implements Serializable interface, MUST have a no-arg constructor. If this is violated, readObject() will produce a java.io.InvalidClassException in runtime

  • All primitive types are serializable.

  • Transient fields (with transient modifier) are NOT serialized, (i.e., not saved or restored). A class that implements Serializable must mark transient fields of classes that do not support serialization (e.g., a file stream).

  • Static fields (with static modifier) are not serialized.

When Object is serialized, Java Runtime associates the serial version number aka, the serialVersionID.

Where we need serialVersionID:

During the deserialization to verify that sender and receiver are compatible with respect to serialization. If the receiver loaded the class with a different serialVersionID then deserialization will end with InvalidClassCastException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named serialVersionUID that must be static, final, and of type long.

Let's try this with an example.

import java.io.Serializable;

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private String empname;
    private byte empage;

    public String getEmpName() {
        return name;
    }

    public void setEmpName(String empname) {
        this.empname = empname;
    }

    public byte getEmpAge() {
        return empage;
    }

    public void setEmpAge(byte empage) {
        this.empage = empage;
    }

    public String whoIsThis() {
        return getEmpName() + " is " + getEmpAge() + "years old";
    }
}

Create Serialize Object

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Writer {
    public static void main(String[] args) throws IOException {
        Employee employee = new Employee();
        employee.setEmpName("Jagdish");
        employee.setEmpAge((byte) 30);

        FileOutputStream fout = new
                FileOutputStream("/users/Jagdish.vala/employee.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fout);
        oos.writeObject(employee);
        oos.close();
        System.out.println("Process complete");
    }
}

Deserialize the object

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Reader {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        Employee employee = new Employee();
        FileInputStream fin = new FileInputStream("/users/Jagdish.vala/employee.obj");
        ObjectInputStream ois = new ObjectInputStream(fin);
        employee = (Employee) ois.readObject();
        ois.close();
        System.out.println(employee.whoIsThis());
    }
}

NOTE: Now change the serialVersionUID of the Employee class and save:

private static final long serialVersionUID = 4L;

And execute the Reader class. Not to execute the Writer class and you will get the exception.

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)

Last updated