The idiomatic way to use atomic operations in Kotlin.
- Code it like
AtomicReference/Int/Long
, but run it in production likeAtomicReference/Int/LongFieldUpdater
. - Use Kotlin-specific extensions (e.g. inline
updateAndGet
andgetAndUpdate
functions). - Compile-time dependency only (no runtime dependencies) on Kotlin/JVM.
- Post-compilation bytecode transformer that declares all the relevant field updaters for you.
- See JVM build setup for details.
- Multiplatform
- Kotlin/JS and Kotlin/Native are supported.
- However, they work as a library dependencies at the moment (unlike Kotlin/JVM).
- This enables writing common Kotlin code with atomics.
Let us declare a top
variable for a lock-free stack implementation:
import kotlinx.atomicfu.atomic // import top-level atomic function from kotlinx.atomicfu
private val top = atomic<Node?>(null)
Use top.value
to perform volatile reads and writes:
fun isEmpty() = top.value == null // volatile read
fun clear() { top.value = null } // volatile write
Use compareAndSet
function directly:
if (top.compareAndSet(expect, update)) ...
Use higher-level looping primitives (inline extensions), for example:
top.loop { cur -> // while(true) loop that volatile-reads current value
...
}
Use high-level update
, updateAndGet
, and getAndUpdate
,
when possible, for idiomatic lock-free code, for example:
fun push(v: Value) = top.update { cur -> Node(v, cur) }
fun pop(): Value? = top.getAndUpdate { cur -> cur?.next } ?.value
Declare atomic integers and longs using type inference:
val myInt = atomic(0) // note: integer initial value
val myLong = atomic(0L) // note: long initial value
Integer and long atomics provide all the usual getAndIncrement
, incrementAndGet
, getAndAdd
, addAndGet
, and etc
operations. They can be also atomically modified via +=
and -=
operators.
- Declare atomic variables as
private val
orinternal val
. You can use just (public)val
, but make sure they are not directly accessed outside of your Kotlin module (outside of the source set). Access to the atomic variable itself shall be encapsulated. - Only simple operations on atomic variables directly are supported.
- Do not read references on atomic variables into local variables,
e.g.
top.compareAndSet(...)
is Ok, whileval tmp = top; tmp...
is not. - Do not leak references on atomic variables in other way (return, pass as params, etc).
- Do not read references on atomic variables into local variables,
e.g.
- Do not introduce complex data flow in parameters to atomic variable operations,
i.e.
top.value = complex_expression
andtop.compareAndSet(cur, complex_expression)
are not supported (more specifically,complex_expression
should not have branches in its compiled representation). - Use the following convention if you need to expose the value of atomic property to the public:
private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore
public var foo: T // public val/var
get() = _foo.value
set(value) { _foo.value = value }
Building with Maven and Gradle is supported for Kotlin/JVM.
Declare AtomicFU version:
<properties>
<atomicfu.version>0.11.2</atomicfu.version>
</properties>
Declare provided dependency on the AtomicFU library (the users of the resulting artifact will not have a dependency on AtomicFU library):
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>atomicfu</artifactId>
<version>${atomicfu.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
Configure build steps so that Kotlin compiler puts classes into a different classes-pre-atomicfu
directory,
which is then transformed to a regular classes
directory to be used later by tests and delivery.
<build>
<plugins>
<!-- compile Kotlin files to staging directory -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<output>${project.build.directory}/classes-pre-atomicfu</output>
<!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code -->
<variant>FU</variant>
</configuration>
</execution>
</executions>
</plugin>
<!-- transform classes with AtomicFU plugin -->
<plugin>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>atomicfu-maven-plugin</artifactId>
<version>${atomicfu.version}</version>
<executions>
<execution>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<input>${project.build.directory}/classes-pre-atomicfu</input>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
You will need Gradle 4.0 or later for the following snippets to work. Add and apply AtomicFU plugin:
buildscript {
ext.atomicfu_version = '0.11.2'
dependencies {
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
}
}
apply plugin: 'kotlinx-atomicfu'
Add compile-only dependency on AtomicFU library and run-time dependency for tests:
dependencies {
compileOnly "org.jetbrains.kotlinx:atomicfu:$atomicfu_version"
testRuntime "org.jetbrains.kotlinx:atomicfu:$atomicfu_version"
}
AtomicFU is also available for Kotlin/JS and Kotlin/Native. If you write
a common code that should get compiled or different platforms, add org.jetbrains.kotlinx:atomicfu-common
to your common code dependencies.
This library is available for Kotlin/JS via Bintray JCenter and Maven Central as
org.jetbrains.kotlinx:atomicfu-js
and via NPM
as kotlinx.atomicfu
.
It is a regular library and you should declare a normal dependency, no plugin is needed nor available.
Both Maven and Gradle can be used.
Since Kotlin/JS does not generally provide binary compatibility between versions, you should use the same version of Kotlin compiler. See gradle.properties.
This library is available for Kotlin/Native via Bintray JCenter and Maven Central as
org.jetbrains.kotlinx:atomicfu-native
.
It is a regular library and you should declare a normal dependency, no plugin is needed nor available.
Only single-threaded code (JS-style) is currently supported.
Kotlin/Native supports only Gradle version 4.7 or later
and you should use kotlin-platform-native
plugin.
First of all, you'll need to enable Gradle metadata in your
settings.gradle
file:
enableFeaturePreview('GRADLE_METADATA')
Then, you'll need to apply the corresponding plugin and add appropriate dependencies in your
build.gradle
file:
buildscript {
repositories {
jcenter()
maven { url 'https://plugins.gradle.org/m2/' }
maven { url 'https://dl.bintray.com/jetbrains/kotlin-native-dependencies' }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version"
}
}
apply plugin: 'kotlin-platform-native'
repositories {
jcenter()
}
dependencies {
implementation 'org.jetbrains.kotlinx:atomicfu-native:0.11.2'
}
sourceSets {
main {
component {
target "ios_arm64", "ios_arm32", "ios_x64", "macos_x64", "linux_x64", "mingw_x64"
outputKinds = [EXECUTABLE]
}
}
}
Since Kotlin/Native does not generally provide binary compatibility between versions,
you should use the same version of Kotlin/Native compiler as was used to build AtomicFU.
Add an appropriate kotlin_native_version
to your gradle.properties
file.
See gradle.properties in AtomicFU project.
AtomicFU provides some additional features that you can optionally use.
AtomicFU also produces code that is using Java 9
VarHandle
instead of AtomicXXXFieldUpdater
and creates JEP 238 multi-release jar file.
You can optionally test lock-freedomness of lock-free data structures using LockFreedomTestEnvironment
class.
See example in LockFreeQueueLFTest
.
Testing is performed by pausing one (random) thread before or after a random state-update operation and
making sure that all other threads can still make progress.
In order to make those test to actually perform lock-freedomness testing you need to configure an additional execution of tests with the original (non-transformed) classes.
For Maven add:
<build>
<plugins>
<!-- additional test execution with surefire on non-transformed files -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>lockfree-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<classesDirectory>${project.build.directory}/classes-pre-atomicfu</classesDirectory>
<includes>
<include>**/*LFTest.*</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
For Gradle there nothing else to add.