March 22, 2007, 1:49 p.m.
posted by oxy
Item 38: Favor the immutable, for it needs no locksAfter reading through this chapter, you may begin to despair—after all, if Java, transactions, or even the EJB Specification can't keep you from having to worry about synchronization concerns (or rather, if the solution yields such unimpressive results that you have to start worrying about them on your own), blissful ignorance no longer remains. You have to start thinking about synchronized blocks, race conditions, deadlock, livelock, and all those other things. One approach that saves you from having to worry about synchronization is to avoid it entirely, by creating objects whose state can never change—by virtue of the fact that an immutable object cannot change its state, synchronization is entirely unnecessary. This avoids the need for synchronized blocks, locks, and thus, contention. Taking this approach brings with it a couple of constraints. First, immutable objects are not easy to build—it's harder than it looks to build an object that doesn't make back doors to its data available to its clients. Second, we often require immutable objects to change state, that is, we want a new immutable object with slightly different state. This requires the construction of an entirely new object, rather than simply teasing the fields in the existing object to come around to the state we're interested in. Building an immutable object is a bit harder than it first appears; see Effective Java [Bloch, Item 12], for some of the details involved. As an additional example, consider our canonical class representing a carbon-based life form on planet Earth:
public class Person
{
public Person(String firstName, String lastName, int age,
Address homeAddress, Person spouse)
{
// Do what you'd expect here—copy parameters to fields
}
public String getFirstName() { return this.firstName; }
public String getLastName() { return this.lastName; }
public int getAge() { return age; }
public Address getHomeAddress() {return this.homeAddress; }
public Person getSpouse() { return spouse; }
}
At a casual glance, the Person class appears to be entirely immutable—there are no setter methods anywhere in the class API, so it's impossible to modify the class's members, right? Quick question: What happens if we do this?
Person p = new Person("Michael", "Neward", 10,
new Address(...), null);
p.getAddress().setStreet("100 White House Way");
Although it's not immediately obvious, the Person class "leaks" a handle to the Address object referenced by the homeAddress field inside of Person. This offers clients the ability to "reach around" the referencing object (Person, in this case) and directly modify the object within. This violates Person's immutability and leaves it vulnerable to synchronization concerns in that Address object. But when we do want to modify a Person's state, instead we must create an entirely new Person instance in which to do this. The simplest approach is to just reuse the constructor, as defined above, like so:
Person p = new Person("Michael", "Neward", 10,
new Address(...), null);
// Michael just had a birthday: increment his age
p = new Person(p.getFirstName(), p.getLastName(),
p.getAge()+1, p.getAddress(), p.getSpouse());
The problem with this approach is that while keeping synchronization out of the picture, it forces the allocation of lots of objects, making more work for the garbage collector, at least in theory. Fortunately, as explained in Item 72, many garbage collectors work well with lots of short-lived temporary objects, so it's not a big deal. If you follow some of the EJB "best practices" books that recommend the use of Data Transfer Objects [Fowler, 401] as the means by which to pass data between an EJB client and the entity that needs to set that data into the database, making the Data Transfer Object an immutable object makes a lot of sense. Similarly, if you decide to pass all data by value between the various layers in your system (see Item 23), making these objects immutable not only ensures that synchronization scenarios can't sneak up on you but also ensures that the by-value object can't be accidentally modified, either. |
- Comment