Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Recovering From Exceptions Inside Java Locks

DZone 's Guide to

Recovering From Exceptions Inside Java Locks

Learn more about recovering from exceptions inside Java locks.

· Java Zone ·
Free Resource

When using locks, you must consider what happens if something fails within the operation inside a lock. Java locks do not apply ACID (atomic, consistent, isolated, durable) rules as databases do. Using explicit or intrinsic locks, you can perform compound actions in isolation. Changes are also durable (if the field declarations allow that). At the same time, locks do not make any guarantees with regards to consistency (valid object state) or atomicity (perform all-or-nothing). 

If an exception is thrown inside a compound action that is guarded by a lock, then everything that happened before the exception will be durable. But the rest of that operation wasn't executed, probably leaving the state of the processed objects in an inconsistent state.

 You should always consider the effect of an exception when using any kind of locking in Java.

Strictly speaking, this does not only apply to code sequences guarded by locks. However, this article covers exception handling inside locks in particular. Let's make this concrete. Here is a broken example of a  synchronized block.

import org.apache.commons.lang3.Validate;
import io.github.benas.randombeans.api.EnhancedRandom;

public class PersonUpdateBroken {

    public static void main(String[] args) {

        Person person = EnhancedRandom.random(Person.class);
        String originalCityValue = person.getCity();

        System.out.println(String.format("Before: %s", person.toString()));
        synchronized (person) {
            try {
                person.setStreet("23535 Michigan Ave");
                person.setPostalcode("MI 48124");
                Validate.validState(1==0);
                person.setCity("Dearborn, USA");
            } catch (Exception e) {
                // NOP
            }
        }
        System.out.println(String.format("After: %s", person.toString()));

        /**
         * The postal code changed to the new postal code
         */
        Validate.validState(person.getPostalcode().equals("MI 48124"));

        /**
         * The city was not updated -> the object is invalid
         */
        Validate.isTrue(person.getCity().equals(originalCityValue));

    }
}


When the exception is thrown in line 16, the operation terminates inside the lock and you can see from the validation ofPerson that it's inconsistent. So, it is important that you recover sensibly if you're getting exceptions inside an operation guarded by a lock. Here is another example that performs a recovery based on a copy of Person.

import org.apache.commons.lang3.Validate;

import io.github.benas.randombeans.api.EnhancedRandom;

public class PersonUpdateRecover {

    public static void main(String[] args) {

        Person person = EnhancedRandom.random(Person.class);

        System.out.println(String.format("Before: %s", person.toString()));
        synchronized (person) {
            final Person copy = new Person(person);
            try {
                person.setStreet("23535 Michigan Ave");
                person.setPostalcode("MI 48124");
                Validate.validState(1==0);
                person.setCity("Dearborn, USA");
            } catch (Exception e) {
                person = copy;
            }
        }
        System.out.println(String.format("After: %s", person.toString()));

        /**
         * The person did not change -> is "valid"
         */
        Validate.validState(!person.getStreet().equals("23535 Michigan Ave"));
        Validate.validState(!person.getPostalcode().equals("MI 48124"));
        Validate.validState(!person.getCity().equals("Dearborn, USA"));


    }
}


As you can see from the validations after the guarded operation, Person was recovered to its initial state. Inside the synchronized block, the initial valid Person is saved. Then, within the catch block, the original reference of the person variable is replaced with a reference stored in the copy variable, which points to the object with the valid original data.

Save your valid state before you perform complex compound actions. Return to that valid state if the operation fails.

It's important to notice that you eventually need a deep clone rather than only a flat copy of the state. Person is a simple object only containing immutable String, so a "flat" copy was enough here. However, if the Person contained more complex mutable state, let's say a collection, it may be necessary to clone Person deeply. Here is some code that implements such cloning:

import org.apache.commons.lang3.Validate;
import com.rits.cloning.Cloner;
import io.github.benas.randombeans.api.EnhancedRandom;

public class PersonUpdateRecoverClone {

    public static void main(String[] args) {

        Cloner cloner = new Cloner();
        Person person = EnhancedRandom.random(Person.class);

        System.out.println(String.format("Before: %s", person.toString()));
        synchronized (person) {
            final Person copy = cloner.deepClone(person);
            try {
                person.setStreet("23535 Michigan Ave");
                person.setPostalcode("MI 48124");
                Validate.validState(1==0);
                person.setCity("Dearborn, USA");
            } catch (Exception e) {
                person = copy;
            }
        }
        System.out.println(String.format("After: %s", person.toString()));

        /**
         * The person did not change -> is "valid"
         */
        Validate.validState(!person.getStreet().equals("23535 Michigan Ave"));
        Validate.validState(!person.getPostalcode().equals("MI 48124"));
        Validate.validState(!person.getCity().equals("Dearborn, USA"));


    }
}


I am using a cloning library that you can find here on GitHub. The  cloner will also handle collections and any other sort of complex state that you eventually need to save before you perform an operation on that considered object.

Now, let's see how all this can be applied to a thread-safe PersonService that can be used safely in an application to maintain the state of persons.

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import com.rits.cloning.Cloner;

@ThreadSafe
public class PersonService {
    @GuardedBy("this")
    private final Map<String, Person> persons;
    private final Cloner cloner = new Cloner();

    ...

    public synchronized Person updateAdress(String id, String street, String postalcode, String city) {
        Person person = persons.get(id);
        Person copy = cloner.deepClone(person);
        try {
            person.setStreet(street);
            person.setPostalcode(postalcode);
            person.setCity(city);
        } catch (Exception e) {
            persons.put(copy.getId(), copy);
            throw new IllegalStateException("update failed", e);
        }
        return new Person(person);
    }

   ...

}


As you can see, updateAdress()(line 18) implements the recovery idiom that I have described to save the state of a person. If the operation fails, then the catch block will replace the invalid version of the person inside thepersons map, and then, it forwards the exception to the client. This way, everything will be safely recovered before an exception will leave everything in an invalid state.

Explicit Locks

In Java, you can also use explicit locks instead of the intrinsic locks that I used above. The idiom how to use explicit locks in Java is often described as follows:

Lock lock = new ReentrantLock();
...
lock.lock()
try {
  // do dome changes to the guarded state
} finally {
  lock.unlock();
}


This idiom is actually not complete. As we've learned previously with intrinsic locks using the  synchronized keyword, we need proper exception handling.

A complete update procedure with Java locks should always include proper exception handling.

So, the complete locking idiom (applied) should look like in the following example:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.Validate;

import io.github.benas.randombeans.api.EnhancedRandom;

public class PersonUpdateRecoverLock {

    public static void main(String[] args) {

        Person person = EnhancedRandom.random(Person.class);
        Lock lock = new ReentrantLock();

        System.out.println(String.format("Before: %s", person.toString()));
        lock.lock();
        try {
            Person copy = new Person(person);
            try {
                person.setStreet("23535 Michigan Ave");
                person.setPostalcode("MI 48124");
                Validate.validState(1 == 0);
                person.setCity("Dearborn, USA");
            } catch (Exception e) {
                person = copy;
            }
        } finally {
            lock.unlock();
        }
        System.out.println(String.format("After: %s", person.toString()));

        /**
         * The person did not change -> is "valid"
         */
        Validate.validState(!person.getStreet().equals("23535 Michigan Ave"));
        Validate.validState(!person.getPostalcode().equals("MI 48124"));
        Validate.validState(!person.getCity().equals("Dearborn, USA"));

    }
}


This example demonstrates the complete explicit locking idiom that includes recovery from exceptions. As in the intrinsic locking examples, clients must save the valid state prior to making any updates. If an exception occurs during the guarded operation (line 20-23), then the initial state must be recovered. The explicit locking idiom completed must look like the following:

Lock lock = new ReentrantLock();
...
lock.lock()
try {
  // save state
  try {
    // do dome changes to the guarded state
  } catch (Exception e) {
    // restore state and eventually throw exception
  }
} finally {
  lock.unlock();
}


The idiom ensures proper recovery operations to take place, and these recovery operations are guarded by the same lock that was used to guard the actual state transition action. Let's look at the PersonService that uses this idiom:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.annotation.concurrent.ThreadSafe;

import com.rits.cloning.Cloner;

@ThreadSafe
public class PersonServiceLock {
    @GuardedBy("lock")
    private final Map<String, Person> persons;
    private final Cloner cloner = new Cloner();
    private final Lock lock = new ReentrantLock();

    ...

    public Person updateAdress(String id, String street, String postalcode, String city) {
        lock.lock();
        try {
            Person person = persons.get(id);
            Person copy = cloner.deepClone(person);
            try {
                person.setStreet(street);
                person.setPostalcode(postalcode);
                person.setCity(city);
                return new Person(person);
            } catch (Exception e) {
                persons.put(copy.getId(), copy);
                throw new IllegalStateException("update failed", e);
            }
        } finally {
            lock.unlock();
        }
    }

    ...

}


Notice that the state should be saved "outside" the actual compound action that changes the state but inside the code block guarded bylock that is responsible for the shared persons collection.

You can find the code examples of this article on my GitHub account.

That's it for today. Hope you liked this post!

Topics:
exception handling in java ,concurrency ,locks ,synchronized ,reentrant lock ,Java ,exceptions ,explicit locks

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}