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 usingAtomicInteger 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.

Article written by Neil Coffey (@BitterCoffey).


The Atomic classes in Java 5 (ctd)

On the previous page, we looked at AtomicInteger and AtomicLong. On this page, we look at three classes that can be useful when you want to couple more than piece of data together and access them atomically.

Note that the classes on this page are probably less used than AtomicInteger and AtomicLong. On the following page, we will discuss a much more important part of the Java concurrency framework, namely the ConcurrentHashMap class.

AtomicReference

The AtomicReference class provides a way to atomically read and set an object reference. It is generally used to tie together more than one atomically accessed variable by wrapping them in animmutable object instance (that is, one whose fields are final).

Suppose, for example, that in a web server application, we want to count the number of page accesses and also the number of accesses that failed due to an error. We can do this by creating a class to encapsulate the current statistics, and then create and update an AtomicReference to an instance of this class:

public class AccessStatistics {
  private final int noPages, noErrors;
  public AccessStatistics(int noPages, int noErrors) {
    this.noPages = noPages;
    this.noErrors = noErrors;
  }
  public int getNoPages() { return noPages; }
  public int getNoErrors() { return noErrors; }
}

Now, to update the statistics, we can use a method such as the following:

private AtomicReference<AccessStatistics> stats =
  new AtomicReference<AccessStatistics>(new AccessStatistics(0, 0));

public void incrementPageCount(boolean wasError) {
  AccessStatistics prev, newValue;
  do {
    prev = stats.get();
    int noPages = prev.getNoPages() + 1;
    int noErrors = prev.getNoErrors;
    if (wasError) {
      noErrors++;
    }
    newValue = new AccessStatistics(noPages, noErrors);
  } while (!stats.compareAndSet(prev, newValue));
}

Atomic references (and atomic classes in general) have the same memory semantics as volatile variables. So when the reference to the new AccessStatistics is set to the AtomicReference, at that moment, the JVM must ensure that the variables set during the constructor are visible to other threads. In other words, it isn't strictly necessary in this case to make the fields on AccessStatistics final. Nonetheless, it's good practice to mark fields on immutable classes as final, and we do so here.

Other uses for AtomicReference

The AtomicReference class can also be useful in cases where you have a structure which is basically read-only but which you very occasionally want to update. On the occasional update, you can replace the old copy with a brand new copy.

AtomicMarkableReference and AtomicStampedReference

If the two variables that you want to 'couple together' are (1) an object reference and (2) a boolean or integer, then two ready-made classes are available: AtomicMarkableReference andAtomicStampedReference to couple an object reference with a boolean and integer respectively. Sometimes these combinations are useful in creating data structures: for example, the boolean flag could indicate whether a node in a tree is marked for deletion; the stamped reference can be used in cases where setting a reference to A, then to B, then back to A again doesn't count as undoing the change.

In principle, these cases could be optimised with help from the JVM. For example, if it is known that an object reference requires 31 bits (some 32-bit systems have a maximum user-mode address space of 2^31 bytes, i.e. no memory pointer needs to be bigger than 31 bits), then the extra bit of a 32-bit word could be used for the boolean flag. And some processors provide CAS instructions that can work on two 32-bit words simultaneously, which could be used to optimise the reference-plus-integer case (or the reference-plus-boolean on a system where a memory pointer needed up to 63 bits).

In practice, no such optimisation exists, at least in Sun's implementation. Current versions simply implement these two classes in a very similar way to our AccessStatistics updater above: each time either the object reference or the boolean/integer is changed, a new instance of an immutable inner class is created to store the two values. So even where you need the functionality of these classes, it may be more convenient, and no less efficient, to roll your own immutable class that has exactly the variables you need.

Next: atomic arrays

If you need atomic access to element of an array, Java 5 provides some classes for efficient atomic array access.

Article written by Neil Coffey (@BitterCoffey).

The atomic arrays: AtomicIntegerArrayAtomicLongArray and AtomicReferenceArray

As well as the atomic integer, long and reference classes described on the previous page, the Java 5 concurrency library provides atomic array implementations of these types. It has the usual set of methods for atomic access such as compareAndSet() and incrementAndGet(), with the first parameter of each being the index into the array.

You could achieve more or less similar functionality by creating, for example, an array of AtomicInteger objects. But these classes are likely to be more efficient on memory and nicer to the garbage collector, since the underlying array is a single object rather than several individual AtomicInteger (etc) objects. Under the hood, these classes actually plug into the JVM in order to atomically access a particular position in a volatile array. In other words, there's no general way exposed by the class libraries to immitate these atomic array classes as efficiently.

If you need an atomic boolean array or atomic bitset, there's none provided as standard. But of course you could use an AtomicIntegerArray and set elements to values of 0 or 1.

Atomic field updaters

A final part of the atomic variable jigsaw in Java 5 are the atomic field updaters. However, they are seldom used and you may wish to skip on to the next section.

Next...

On the next page, we move on to look at Java 5's concurrent collections, with arguably the most important, the ConcurrentHashMap class.

Article written by Neil Coffey (@BitterCoffey).


ConcurrentHashMap

Key structures implemented as part of the Java Collections API are various types of maps, and in particular the hash map (via the HashMap class and other related classes).

Maps allow you to associate keys with values and crop up in all sorts of uses such as:

  • Caches: for example, after reading the contents of a given file or database table, we could associate the file name with its contents (or database key with a representation of the row data) in a HashMap;
  • Dictionaires: for example, we could associate locale abbrevations with a language name;
  • Sparse arrays: by mapping integers to values, we in effect create an array which does not waste space on blank elements.

Frequently-accessed hash maps can be important on server applications for caching purposes. And as such, they can receive a good deal of concurrent access. Before Java 5, the standard HashMapimplementation had the weakness that accessing the map concurrently meant synchronizing on the entire map on each access. This means that, for example, a frequently-used cache implemented as a hash map can encounter high contention: multiple threads attempting to access the map at the same time frequently have to block waiting for one another.

Lock striping and ConcurrentHashMap

Synchronizing on the whole map fails to take advantage of a possible optimisation: because hash maps store their data in a series of separate buckets, it is in principle possible to lock only the portion of the map that is being accessed. This optimisation is generally called lock striping.

Java 5 brings a hash map optimised in this way in the form of ConcurrentHashMap. A combination of lock striping plus judicious use of volatile variables gives the class two highly concurrent properties:

  • Writing to a ConcurrentHashMap locks only a portion of the map;
  • Reads can generally occur without locking.

Next: throughput and scalability of ConcurrentHashMap vs synchronized HashMap

The benefits of ConcurrentHashMap over a regular synchronized HashMap become blatantly apparent when we run a small experiment to simulate what might happen in the case of a map used as a frequently-accessed cache on a moderately busy server. On the next page, we discuss the scalability of ConcurrentHashMap in the light of such a simulation.

Article written by Neil Coffey (@BitterCoffey).


Reference: http://www.javamex.com/tutorials/synchronization_concurrency_7_atomic.shtml

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值