commit | 1dd182d6d185f5e425f86d206532034a4a4911ef | [log] [tgz] |
---|---|---|
author | Roman Elizarov <elizarov@gmail.com> | Wed Jul 25 16:56:11 2018 +0300 |
committer | Roman Elizarov <elizarov@gmail.com> | Thu Aug 09 17:25:23 2018 +0300 |
tree | 5a71dc520bf90e771e928a13d70a40a7745481c9 | |
parent | 5f18ab9e766abaab0da04aaf718333603ebef228 [diff] |
Typo in docs fixed
The idiomatic way to use atomic operations in Kotlin.
AtomicReference/Int/Long
, but run it in production like AtomicReference/Int/LongFieldUpdater
.updateAndGet
and getAndUpdate
functions).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) // must be declared as private val with initializer
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.
private val
. You can use just (public) val
in nested classes, but make sure they are not accessed outside of your Kotlin source file.top.compareAndSet(...)
is Ok, while val tmp = top; tmp...
is not.top.value = complex_expression
and top.compareAndSet(cur, complex_expression)
are not supported (more specifically, complex_expression
should not have branches in its compiled representation).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.0</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.0' dependencies { classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version" } } apply plugin: 'kotlinx-atomicfu'
Add compile-only dependency on AtomicFU library:
dependencies { compileOnly "org.jetbrains.kotlinx:atomicfu:$atomicfu_version" }
Install bytecode transformation pipeline so that compiled classes from classes
directory get transformed to a different classes-post-atomicfu
directory to be used later by tests and delivery.
atomicFU { inputFiles = sourceSets.main.output.classesDirs outputDir = file("$buildDir/classes-post-atomicfu/main") classPath = sourceSets.main.runtimeClasspath variant = "FU" // "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code } atomicFU.dependsOn compileKotlin testClasses.dependsOn atomicFU jar.dependsOn atomicFU jar { mainSpec.sourcePaths.clear() // hack to clear existing paths from files(atomicFU.outputDir, sourceSets.main.output.resourcesDir) }
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.0' } 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 can produce code that is using Java 9 VarHandle instead of AtomicXXXFieldUpdater
. Set variant
configuration option to VH
.
You can also create JEP 238 multi-release jar with both AtomicXXXFieldUpdater
baseline and VarHandle
version for Java 9+. Set variant
configuration option to BOTH
and configure Multi-Release: true
attribute in the resulting jar manifest.
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 add:
dependencies { testRuntime "org.jetbrains.kotlinx:atomicfu:$atomicfu_version" } task lockFreedomTest(type: Test, dependsOn: testClasses) { include '**/*LFTest.*' }