Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Azeez Olaniran
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Azeez Olaniran

Using Java's AtomicInteger To Loop Over A Range Of Numbers In ColdFusion

By
Published in Comments (7)

The other day, as part of my cuid for ColdFusion implementation, I had to create an internal counter that looped over a static range of numbers. Once the counter reached its upper-bound, it reset back to zero. Since this was operating in a multi-threaded environment, I decided to use Java's AtomicInteger such that I could keep the counter thread-safe without having to add any additional locking in my code. Out of the box, the AtomicInteger doesn't loop - it just increments indefinitely. As such, I wanted to put together a quick demo, more as a mental note, on how the AtomicInteger could be used to loop over a range of numbers in ColdFusion.

The entire java.util.concurrent.atomic package in Java is based around the notion of compareAndSet(). This method uses machine-level locking to set a value only if the current value matches the expected precondition. The compareAndSet() method returns a Boolean, True if the "set" operation succeeded; or, False if the "set" operation failed the precondition. As such, this method can be executed inside a loop that will continuously try running the compareAndSet() operation until is succeeds.

To experiment with using the AtomicInteger class to iterate over a range of values, I've created a ColdFusion component called AtomicRange.cfc. The AtomicRange.cfc uses an AtomicInteger internally. However, rather than just calling the various increment methods on the AtomicInteger instance, I'm using the ".compareAndSet()" method inside of a while(true) loop. This way, I can have explicit control over which values will be set, resetting the internal counter when it has surpassed its upper-bound.

Here's the code for my AtomicRange.cfc ColdFusion component:

component
	output = false
	hint = "I provide a thread-safe counter over an inclusive range of integers."
	{

	PRE_TYPE = 0;
	POST_TYPE = 1;

	/**
	* I initialize the range with the given limits. If only one limit is provided, the
	* range will start at zero and go up to the given limit. If two limits are provided,
	* the range will use the first as the lower limit and the second as the upper limit.
	*
	* @limitOne I am the lower limit or the upper limit, depending on how many limits are provided.
	* @limitTwo I am the upper limit, if provided.
	* @output false
	*/
	public any function init(
		required numeric limitOne,
		numeric limitTwo
		) {

		// If we only have one limit provided, then the provided limit will act as the
		// upper bound of the range, starting at zero.
		if ( isNull( limitTwo ) ) {

			rangeMin = 0;
			rangeMax = fix( limitOne );

		// If two limits are provided, then the range will iterate from the first limit
		// to the second limit.
		} else {

			rangeMin = fix( limitOne );
			rangeMax = fix( limitTWo );

		}

		// I determine if the range is incrementing or decrementing.
		rangeStep = ( rangeMin <= rangeMax )
			? 1
			: -1
		;

		counter = createObject( "java", "java.util.concurrent.atomic.AtomicInteger" )
			.init( javaCast( "int", rangeMin ) )
		;

	}

	// ---
	// PUBLC METHODS.
	// ---

	/**
	* I return the current value of the atomic range.
	*
	* @output false
	*/
	public numeric function get() {

		return( counter.get() );

	}


	/**
	* I increment the atomic range and return the pre-increment value.
	*
	* @output false
	*/
	public numeric function getAndIncrement() {

		return( incrementAndReturn( PRE_TYPE ) );

	}


	/**
	* I increment the atomic range and return the post-increment value.
	*
	* @output false
	*/
	public numeric function incrementAndGet() {

		return( incrementAndReturn( POST_TYPE ) );

	}

	// ---
	// PRIVATE METHODS.
	// ---

	/**
	* I increment the internal counter and return the "pre" or "post" increment value.
	*
	* @returnType I determine which value to return in the incrementing work-flow.
	* @output false
	*/
	private numeric function incrementAndReturn( required numeric returnType ) {

		// The "atomic" data types are based on the idea of volatile properties that can
		// be accessed and mutated without any additional locks. They center around this
		// concept of "compare and set" operations, where we can keep trying to perform
		// an operation until it is completed successfully.
		while ( true ) {

			var currentValue = counter.get();
			var nextValue = ( currentValue + rangeStep );

			// Counting up - check to see if we need to reset the range.
			if ( rangeStep == 1 ) {

				if ( nextValue > rangeMax ) {

					nextValue = rangeMin;

				}

			// Counting down - check to see if we need to reset the range.
			} else {

				if ( nextValue < rangeMax ) {

					nextValue = rangeMin;

				}

			}

			// Try to set the next value. This will only succeed if the counter is
			// currently in a known state; otherwise, the command will be ignored,
			// returning False.
			if ( counter.compareAndSet( javaCast( "int", currentValue ), javaCast( "int", nextValue ) ) ) {

				// At this point, the "set" operation succeeded in changing the
				// underlying atomic integer. Now, we just need to return the desired
				// value in the work-flow.
				return( ( returnType == PRE_TYPE ) ? currentValue : nextValue );

			}

		}

	}

}

As you can see, this ColdFusion component has almost no logic outside of the internal while(true) loop. And, inside of that while(true) loop, I'm calculating what would be the next value of the range-based integer. I then use the .compareAndSet() method, which will only succeed if the counter is in a "known" state when I perform the set operation.

To test this, we can call the AtomicRange.cfc a finite number of times. And, since we know the upper and lower bounds of the range, we should be able to predict what the final value of the counter will be once we're done incrementing. Assuming, of course, that the AtomicRange.cfc is thread-safe.

And, to test the thread-safety of the AtomicRange.cfc, we can attempt to mutate the counter across parallel CFThread bodies:

<cfscript>

	// For the testing, we're going to assume that the range is always increasing. That
	// just makes our math easier to calculate.
	rangeMin = 1;
	rangeMax = 10;
	range = new AtomicRange( rangeMin, rangeMax );

	// Define the parallel testing threads.
	testThreadCount = 11;
	testIncrementCount = 173357;

	// Since we know the number of threads that we're going to run and the number of
	// calls to the "increment" method inside each thread, we can calculate the total
	// number of "increment" calls that will be made against the AtomicRange. And, from
	// that, we can use the modulo operator to figure out how many times the range should
	// loop back on itself.
	rangeDelta = ( rangeMax - rangeMin + 1 );
	incrementCount = ( testThreadCount * testIncrementCount );
	expectedValue = ( rangeMin + ( incrementCount % rangeDelta ) );

	// Spawn the asynchronous threads.
	for ( i = 0 ; i < testThreadCount ; i++ ) {

		thread
			name = "parallel-counter-#i#"
			action = "run"
			testIncrementCount = testIncrementCount
			{

			for ( var r = 0 ; r < testIncrementCount ; r++ ) {

				range.getAndIncrement();

			}

		}

	}

	// Block / wait for the asynchronous threads to return.
	thread action = "join";

	writeOutput( "Increment count: " & incrementCount & "<br />" );
	writeOutput( "Expecting value: " & expectedValue & "<br />" );
	writeOutput( "Current value: " & range.get() & "<br />" );

</cfscript>

As you can see here, I'm using a number of asynchronous threads to try and operate on the AtomicRange.cfc instance in parallel, putting the thread-safety to test. And, when we run the above code in ColdFusion, we get the following output:

Increment count: 1906927
Expecting value: 8
Current value: 8

As you can see, the predicted value matches the actual value. At just under 2-million operations performed across 9 asynchronous threads, the AtomicRange.cfc performed in a thread-safe manner.

I've only used Java's Atomic data-types a handful of times. And, in fact, I've only ever used the AtomicInteger specifically. It's awesome that I can use it to loop over a range of values; but, I'm wondering what other kinds of magic I may be able to extract from the Atomic package. I'll have to carve out some time to dig into this package a bit deeper.

Want to use code from this post? Check out the license.

Reader Comments

198 Comments

@Ben:

It appears you still have a race condition around the following statement:

var currentValue = counter.get();

The problem is you could have multiple threads read this input and get back a value (let's say 4).

The first thread that hits compareAndSet() will succeed, but the other threads will always return false because the currentValue has incremented, so they'll still in the loop forever.

15,902 Comments

@Dan,

Ah, but they will re-calculate the current / next in the next iteration of the while(true) loop. So, eventually, they will make it through.

15,902 Comments

@All,

After I posted this, my co-worker Ben Darfler suggested that I compare the speed of this .compareAndSet() approach to just having an ever-increasing counter with a modulus operation over the static range. So, this morning, I attempted to compare the two on my local development environment:

www.bennadel.com/blog/3427-using-an-atomiclong-with-a-modulo-operation-may-be-faster-than-using-compareandset-for-thread-safe-range-based-counters.htm

Turns out, the modulo approach is twice as fast (at least on my machine). And, IMO, easier to read and to understand. Very cool!

15,902 Comments

@BR,

That's really interesting. To be honest, I don't actually know that much about Java - I primarily access it via ColdFusion, which actually compiles each method down to its own class. As such, I don't think I can actually -- or "easily" -- use that approach in ColdFusion. But, it's fascinating.

I've never seen that "::" syntax before. I assume it is a "bind" operator, since I know JavaScript has that in a current ES proposal. I didn't realize that existed in other languages. And, interesting the way that you don't need "this." inside the bound method.

Thanks for sharing -- I'd like to see if I can get something like that working in ColdFusion, just for the sake of understanding how "free" methods are used in Java.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel