lunedì 10 marzo 2008

Atomic Variables

The following tip examines new concurrency support in JDK 5.0: atomic variables.

You should be aware of the risks of sharing variables across different threads. It's important that you restrict access to shared variables by ensuring that only one thread changes a shared variable at a time. This is typically done by wrapping critical code sections with a synchronized block, such that only one thread is in a protected section at time.

This technique works well, however synchronizing code adds to the runtime costs of your program. It takes time to get the synchronized lock, modify the variable, and then release the lock. It is quicker if you can skip the use of locks for simple variable updates, or simply have lock-free algorithms to begin with. But, you can't just remove the synchronized block without replacing it with something else.

JDK 5.0 offers a way to meet these needs through the new java.util.concurrent.atomic package. The classes in the package allow you to atomically access variables of the designated type. They also offer methods for atomic get-and-set type operations.

The package includes an AtomicInteger class for atomically updating integer values, AtomicLong for atomically updating long values, AtomicBoolean for basic boolean operations, and AtomicReference for atomic object comparisons and settings. There are also classes for special handling of arrays: AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray.

To get started with atomic variables, let take a look at the AtomicInteger class. Essentially, this class works like a wrapped integer value. You get the value with the class's get() method, and set it with the set() method. You can also get and set the value in one step with the getAndSet() method -- this eliminates any risk of another thread changing the value between your call to get and your call to set.

The basic get and set operations on an integer work as follows, where myVariable is the variable to manipulate:

   // Save off old value
int oldValue = myVariable;

// Change to new value
myVariable = oldValue + 1;

If you don't put these lines of code into a synchronized block, it is possible for the thread scheduler to interrupt in the middle of the two statements. If so, the change to myVariable happens on the original value of myVariable, not the updated version. There is a similar problem in using the ++ auto-increment operator. Short of putting the ++ usage in a synchronized block, there's no way to ensure that the auto-increment operation is atomic.

To prevent these problems, use one of the new atomic methods in AtomicInteger. The methods let you combine set or get operations with one of several different methods:

  • addAndGet()
  • getAndAdd()
  • decrementAndGet()
  • getAndDecrement()
  • incrementAndGet()
  • getAndIncrement()
  • getAndSet()

Why two versions for most of these methods? When "get" is first, the value returned is the original value. When "get" is second, the value returned is the new, adjusted value. So, for an AtomicInteger with a value of 10, getAndIncrement() returns 10, and incrementAndGet() returns 11. In both cases, the value of the AtomicInteger is 11 after the call.

Another method in AtomicInteger worth mentioning is compareAndSet(int expect, int update). This method allows you to check if the value of the AtomicInteger is the expected value, and if it is, change the AtomicInteger to the new updated value. In fact, nearly all the methods previously mentioned are internally implemented with compareAndSet().

To demonstrate the value of the classes in the atomic package, consider the following. Say you had a property that was protected by a synchronized setter/getter pair of methods:

  public class MyLong1 {
private long seed;
public synchronized void setSeed(long seed) {
this.seed = seed;
}
public synchronized long getSeed() {
return seed;
}
}

With the use of AtomicLong and its related classes, you can change this to use an unsynchronized version:

  public class MyLong2 {
private AtomicLong seed;
public void setSeed(long seed) {
this.seed.set(seed);
}
public long getSeed() {
return seed.get();
}


}

Notice that the assignment statement in the setter method changed to a call to the set() method of AtomicLong, and the getter method calls the get() method of AtomicLong. The use of AtomicLong here removes the need to synchronize the methods. In the specific case of the java.util.Random class, the setSeed() method is still synchronized due to other aspects of the method, not for the benefit of the seed property.

As a simple rule, if you create synchronized blocks for accessing variables of type int, long, or boolean, consider swapping the synchronized block for an atomic variable. For more complex types, create your own synchronized type with the help of the AtomicReference class.

http://java.sun.com/developer/JDCTechTips/2005/tt0419.html#2

Nessun commento: