| Base class for simple, small classes
maintaining single values that are always accessed
and updated under synchronization. Since defining them for only
some types seemed too arbitrary, they exist for all basic types,
although it is hard to imagine uses for some.
These classes mainly exist so that you do not have to go to the
trouble of writing your own miscellaneous classes and methods
in situations including:
- When you need or want to offload an instance
variable to use its own synchronization lock.
When these objects are used to replace instance variables, they
should almost always be declared as
final . This
helps avoid the need to synchronize just to obtain the reference
to the synchronized variable itself.
- When you need methods such as set, commit, or swap.
Note however that
the synchronization for these variables is independent
of any other synchronization perfromed using other locks.
So, they are not
normally useful when accesses and updates among
variables must be coordinated.
For example, it would normally be a bad idea to make
a Point class out of two SynchronizedInts, even those
sharing a lock.
- When defining
static variables. It almost
always works out better to rely on synchronization internal
to these objects, rather than class locks.
While they cannot, by nature, share much code,
all of these classes work in the same way.
Construction
Synchronized variables are always constructed holding an
initial value of the associated type. Constructors also
establish the lock to use for all methods:
- By default, each variable uses itself as the
synchronization lock. This is the most common
choice in the most common usage contexts in which
SynchronizedVariables are used to split off
synchronization locks for independent attributes
of a class.
- You can specify any other Object to use as the
synchronization lock. This allows you to
use various forms of `slave synchronization'. For
example, a variable that is always associated with a
particular object can use that object's lock.
Update methods
Each class supports several kinds of update methods:
- A
set method that sets to a new value and returns
previous value. For example, for a SynchronizedBoolean b,
boolean old = b.set(true) performs a test-and-set.
- A
commit method that sets to new value only
if currently holding a given value.
For example, here is a class that uses an optimistic update
loop to recompute a count variable represented as a
SynchronizedInt.
class X {
private final SynchronizedInt count = new SynchronizedInt(0);
static final int MAX_RETRIES = 1000;
public boolean recomputeCount() throws InterruptedException {
for (int i = 0; i < MAX_RETRIES; ++i) {
int current = count.get();
int next = compute(current);
if (count.commit(current, next))
return true;
else if (Thread.interrupted())
throw new InterruptedException();
}
return false;
}
int compute(int l) { ... some kind of computation ... }
}
- A
swap method that atomically swaps with another
object of the same class using a deadlock-avoidance strategy.
- Update-in-place methods appropriate to the type. All
numerical types support:
- add(x) (equivalent to return value += x)
- subtract(x) (equivalent to return value -= x)
- multiply(x) (equivalent to return value *= x)
- divide(x) (equivalent to return value /= x)
Integral types also support:
- increment() (equivalent to return ++value)
- decrement() (equivalent to return --value)
Boolean types support:
- or(x) (equivalent to return value |= x)
- and(x) (equivalent to return value &= x)
- xor(x) (equivalent to return value ^= x)
- complement() (equivalent to return x = !x)
These cover most, but not all of the possible operators in Java.
You can add more compute-and-set methods in subclasses. This
is often a good way to avoid the need for ad-hoc synchronized
blocks surrounding expressions.
Guarded methods
All Waitable subclasses provide notifications on
every value update, and support guarded methods of the form
when predicate, that wait until the
predicate hold, then optionally run any Runnable action
within the lock, and then return. All types support:
- whenEqual(value, action)
- whenNotEqual(value, action)
(If the action argument is null, these return immediately
after the predicate holds.)
Numerical types also support
- whenLess(value, action)
- whenLessEqual(value, action)
- whenGreater(value, action)
- whenGreaterEqual(value, action)
The Waitable classes are not always spectacularly efficient since they
provide notifications on all value changes. They are
designed for use in contexts where either performance is not an
overriding issue, or where nearly every update releases guarded
waits anyway.
Other methods
This class implements Executor, and provides an execute
method that runs the runnable within the lock.
All classes except SynchronizedRef and WaitableRef implement
Cloneable and Comparable .
Implementations of the corresponding
methods either use default mechanics, or use methods that closely
correspond to their java.lang analogs. SynchronizedRef does not
implement any of these standard interfaces because there are
many cases where it would not make sense. However, you can
easily make simple subclasses that add the appropriate declarations.
[ Introduction to this package. ]
|