Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A transient final field used as a lock is null

The following code throws a NullPointerException.

import java.io.*;

public class NullFinalTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Foo foo = new Foo();
        foo.useLock();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        new ObjectOutputStream(buffer).writeObject(foo);
        foo = (Foo) new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())).readObject();
        foo.useLock();
    }

    public static class Foo implements Serializable {
        private final String lockUsed = "lock used";
        private transient final Object lock = new Object();
        public void useLock() {
            System.out.println("About to synchronize");
            synchronized (lock) { // <- NullPointerException here on 2nd call
                System.out.println(lockUsed);
            }
        }
    }
}

Here is the output:

About to synchronize
lock used
About to synchronize
Exception in thread "main" java.lang.NullPointerException
    at NullFinalTest$Foo.useLock(NullFinalTest.java:18)
    at NullFinalTest.main(NullFinalTest.java:10)

How can lock possibly be null?

like image 698
Brandon Avatar asked Sep 07 '12 19:09

Brandon


2 Answers

A transient final field used as a lock is null

Here are few facts about the transient variable:

- Transient keyword when used on an instance variable, will prevent that instance variable to be serialized.

- On De-serialization, the transient variable get to their Default values.....

Eg:

  • Object Reference Variable to null
  • int to 0
  • boolean to false, etc.......

So thats the reason you are getting a NullPointerException, when deserializing it...

like image 89
Kumar Vivek Mitra Avatar answered Sep 26 '22 23:09

Kumar Vivek Mitra


Any field that is declared transient is not serialized. Moreover, according to this blog post, field values are not even initialized to the values that would be set by a default constructor. This creates a challenge when a transient field is final.

According to the Serializable javadoc, deserialization can be controlled by implementing the following method:

private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException;

I came up with the following solution, based on this excellent StackOverflow answer:

import java.io.*;
import java.lang.reflect.*;

public class NullFinalTestFixed {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Foo foo = new Foo();
        foo.useLock();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        new ObjectOutputStream(buffer).writeObject(foo);
        foo = (Foo) new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())).readObject();
        foo.useLock();
    }

    public static class Foo implements Serializable {
        private final String lockUsed = "lock used";
        private transient final Object lock = new Object();
        public void useLock() {
            System.out.println("About to synchronize");
            synchronized (lock) {
                System.out.println(lockUsed);
            }
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            initLocks(this, "lock");
        }
    }

    public static void initLocks(Object obj, String... lockFields) {
        for (String lockField: lockFields) {
            try {
                Field lock = obj.getClass().getDeclaredField(lockField);
                setFinalFieldValue(obj, lock, new Object());
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void setFinalFieldValue(Object obj, Field field, Object value) {
        Exception ex;
        try {
            field.setAccessible(true);
            Field modifiers = Field.class.getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(obj, value);
            return;
        } catch (IllegalAccessException e) {
            ex = e;
        } catch (NoSuchFieldException e) {
            ex = e;
        }
        throw new RuntimeException(ex);
    }
}

Running it results in the following output (no NullPointerException):

About to synchronize
lock used
About to synchronize
lock used
like image 32
Brandon Avatar answered Sep 24 '22 23:09

Brandon