The Atomic classes in Java 5: AtomicInteger and AtomicLong
The AtomicInteger class has a number of uses, but one is a drop-in replacement
for an atomic counter. Before Java 5, we had to write classes with access to the counter variable
in synchronized blocks or methods, or else use a volatile variable which
is a lighter form of synchronization but with the risk that some updates could be missed if they
happen concurrently. An AtomicInteger can be used as a drop-in replacement that
provides the best of both worlds:
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void incrementCount() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
The significant feature of AtomicInteger which we exploit is the call to
incrementAndGet(). This method wraps round a machine instruction (or instructions)
similar to CAS which will read, increment and set the underlying value in memory as a
'locked together' (atomic) action. Notice that this method returns the new incremented value,
although we ignore it. On the other hand, if we were using AtomicInteger to do
something like provide the next key to store in a database, we would probably want to
take notice of the return value.
Unsurprisingly, AtomicLong provides similar atomic access to an underlying
long variable.
Example: an atomic bitset to manage a database connection pool
In reality, our new Counter class is a bit pointless, because instead of an instance of
Counter we may as well just use an instance of AtomicInteger as our
counter! Let's consider a case where we might want to wrap additional functionality
round an AtomicInteger (or AtomicLong).
Consider the case where we have a small list of resources, e.g. database connections,
which we need to atomically "take" and "put back". If the number of available resources is
smaller than 32, we can represent each one as a single bit of an AtomicInteger
(or, with an AtomicLong, we could have up to 64). To take a resource from
the pool, we need to atomically find the next unset bit and set it; to return the
resource to the pool, we need to atomically unset the bit that we set before. We'll
use an inner class to associate a Connection object with its position in
the array. So our overall class will look like this:
public class ConnectionPool {
public static class PooledConnection {
private Connection conn;
private int index;
private PooledConnection(Connection conn, int index) { ... }
public Connection getConnection() { return conn; }
}
private PooledConnection[] connections;
private AtomicInteger usedConnections;
public PooledConnection getConnection() {
// ... find and return a connection whose bit in 'usedConnections' is not set
}
public void returnConnection(PooledConnection conn) {
// ... look at conn.index and unset the corresponding bit in 'usedConnections'
}
}
A client that needed a database connection would then do something like
this:
PooledConnection pooledConn = pool.getConnection();
try {
Connection c = pooledConn.getConnection();
// run some SQL on c
} finally {
pool.returnConnection(pooledConn);
}
Now, back to our ConnectionPool class. To 'get' a free connection, we
need to atomically do the following:
- get the current integer value of usedConnections;
- find the first unset bit in that integer;
- make a note of the index, n, of that bit, and set the bit;
- return the PooledConnection from the nth position in the array.
To do all this atomically, our getConnection() implementation
can look like the following example. We assume that we have a method firstUnsetBit(),
which will find the index of the first bit that is 0 in a given integer,
or return -1 if all bits are set. (We won't worry about the implementation
of this method here, except to note that it is something that we can
optimise: with an AND operation, we can test for several bits at once.)
public PooledConnection getConnection() {
PooledConnection ret = null;
do {
int previousBits = usedConnections.get();
int connectionNo = firstUnsetBit(previousBits);
if (connectionNo == -1 || connectionNo >= NO_AVAILABLE_CONNECTIONS) {
// If none currently available, just return null for now. Later, we'll
// improve to wait for a connection to become available.
return null;
}
int newBits = previousBits | (1 << connectionNo);
if (usedConnections.compareAndSet(previousBits, newBits)) {
ret = connections[connectionNo];
}
} while (ret == null);
return ret;
}
The key call is to usedConnections.compareAndSet(). After we've found
the first free bit and calculated what the new bit mask should be, we
use this call to atomically check that the current value of the bit mask is still
what we just read it to be (i.e. no other thread has just "snuck in" while we
were find the free bit and calculating the new bit mask) and if so set the new
value. In the unlikely event that another thread did sneak in, this
method returns false and we loop round and try again until we succeeed,
or until we find that there is no longer a free bit available (in which case
we return null). The key thing is that we hold on to the CPU the whole
time. We don't have to suspend our thread and delay for several
milliseconds– as could have
been the case if we'd used synchronized– just because another thread was simultaneously getting another
connection.
The code to release a connection has similar logic. We read the current value
of the usedConnections bit mask, clear the bit representing the
connection we're returning, and then call compareAndSet. On the
very unlikely occasion that this fails due to somebody sneaking in, we loop round
until we're successful.
public void returnConnection(PooledConnection conn) {
int bitNo = conn.index;
int previousBits, newBits;
do {
previousBits = usedConnections.get();
// Clear the relevant bit
newBits = previousBits & ~(1 << bitNo);
// Try to set the new bit mask, and loop round until successful
} while (!usedConnections.compareAndSet(previousBits, newBits));
}
Note that our connection pool as it stands has limitations. If on a call to
getConnection(), all connections are currently in use, our method
simply returns null. In real life, we might want to wait (for some limited
time) for a connection to become available. We'll see later that Java 5
provides the Semaphore class which can help us with this.
Next...
On the next page, we look at the AtomicReference
class, which provides similar functionality as AtomicInteger and AtomicLong, but allows
us to define a wrapper class to bind several variables together atomically.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.