BAEL-1423 Java Concurrency Utility with JCTools Library
This commit is contained in:
		
							parent
							
								
									323f5bf370
								
							
						
					
					
						commit
						f4feab9212
					
				
							
								
								
									
										11
									
								
								jctools/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								jctools/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| ## Overview | ||||
| 
 | ||||
| This project holds a [couple of tests](./src/test/java/com/baeldung/jctools/JCToolsUnitTest.java) which illustrate JCTools specifics and a [benchmark](./src/main/java/com/baeldung/jctools/MpmcBenchmark.java) in the [JMH](http://openjdk.java.net/projects/code-tools/jmh/) format.   | ||||
| 
 | ||||
| ## How to build and run the JMH benchmark | ||||
| 
 | ||||
| Execute the following from the project's root:   | ||||
| ```bash | ||||
| mvn clean install | ||||
| java -jar ./target/benchmarks.jar MpmcBenchmark -si true | ||||
| ``` | ||||
							
								
								
									
										108
									
								
								jctools/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								jctools/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <groupId>com.baeldung</groupId> | ||||
|     <artifactId>jctools</artifactId> | ||||
|     <version>0.0.1-SNAPSHOT</version> | ||||
| 
 | ||||
|     <name>jctools</name> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>org.jctools</groupId> | ||||
|             <artifactId>jctools-core</artifactId> | ||||
|             <version>${jctools.version}</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>org.openjdk.jmh</groupId> | ||||
|             <artifactId>jmh-core</artifactId> | ||||
|             <version>${jmh.version}</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>org.openjdk.jmh</groupId> | ||||
|             <artifactId>jmh-generator-annprocess</artifactId> | ||||
|             <version>${jmh.version}</version> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>junit</groupId> | ||||
|             <artifactId>junit</artifactId> | ||||
|             <version>4.12</version> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <dependency> | ||||
|             <groupId>org.assertj</groupId> | ||||
|             <artifactId>assertj-core</artifactId> | ||||
|             <version>${assertj.version}</version> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
|     <properties> | ||||
|         <jctools.version>2.1.2</jctools.version> | ||||
|         <assertj.version>3.9.1</assertj.version> | ||||
|         <jmh.version>1.20</jmh.version> | ||||
| 
 | ||||
|         <javac.target>1.8</javac.target> | ||||
| 
 | ||||
|         <uberjar.name>benchmarks</uberjar.name> | ||||
|     </properties> | ||||
| 
 | ||||
|     <build> | ||||
|         <plugins> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-compiler-plugin</artifactId> | ||||
|                 <version>3.1</version> | ||||
|                 <configuration> | ||||
|                     <compilerVersion>${javac.target}</compilerVersion> | ||||
|                     <source>${javac.target}</source> | ||||
|                     <target>${javac.target}</target> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
| 
 | ||||
|             <!-- Borrowed from the 'jmh-java-benchmark-archetype' pom.xml --> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-shade-plugin</artifactId> | ||||
|                 <version>2.2</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <phase>package</phase> | ||||
|                         <goals> | ||||
|                             <goal>shade</goal> | ||||
|                         </goals> | ||||
|                         <configuration> | ||||
|                             <finalName>${uberjar.name}</finalName> | ||||
|                             <transformers> | ||||
|                                 <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> | ||||
|                                     <mainClass>org.openjdk.jmh.Main</mainClass> | ||||
|                                 </transformer> | ||||
|                             </transformers> | ||||
|                             <filters> | ||||
|                                 <filter> | ||||
|                                     <!-- | ||||
|                                         Shading signed JARs will fail without this. | ||||
|                                         http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar | ||||
|                                     --> | ||||
|                                     <artifact>*:*</artifact> | ||||
|                                     <excludes> | ||||
|                                         <exclude>META-INF/*.SF</exclude> | ||||
|                                         <exclude>META-INF/*.DSA</exclude> | ||||
|                                         <exclude>META-INF/*.RSA</exclude> | ||||
|                                     </excludes> | ||||
|                                 </filter> | ||||
|                             </filters> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|         </plugins> | ||||
|     </build> | ||||
| 
 | ||||
| </project> | ||||
| @ -0,0 +1,98 @@ | ||||
| package com.baeldung.jctools; | ||||
| 
 | ||||
| import org.jctools.queues.MpmcArrayQueue; | ||||
| import org.jctools.queues.atomic.MpmcAtomicArrayQueue; | ||||
| import org.openjdk.jmh.annotations.*; | ||||
| import org.openjdk.jmh.infra.Control; | ||||
| 
 | ||||
| import java.util.Queue; | ||||
| import java.util.concurrent.ArrayBlockingQueue; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| @BenchmarkMode(Mode.SampleTime) | ||||
| @OutputTimeUnit(TimeUnit.NANOSECONDS) | ||||
| @Fork(1) | ||||
| @Warmup(iterations = 1) | ||||
| @Measurement(iterations = 3) | ||||
| public class MpmcBenchmark { | ||||
| 
 | ||||
|     public static final String GROUP_UNSAFE = "MpmcArrayQueue"; | ||||
|     public static final String GROUP_AFU = "MpmcAtomicArrayQueue"; | ||||
|     public static final String GROUP_JDK = "ArrayBlockingQueue"; | ||||
| 
 | ||||
|     public static final int PRODUCER_THREADS_NUMBER = 32; | ||||
|     public static final int CONSUMER_THREADS_NUMBER = 32; | ||||
| 
 | ||||
|     public static final int CAPACITY = 128; | ||||
| 
 | ||||
|     @State(Scope.Group) | ||||
|     public static class Mpmc { | ||||
|         public final Queue<Long> queue = new MpmcArrayQueue<>(CAPACITY); | ||||
|     } | ||||
| 
 | ||||
|     @State(Scope.Group) | ||||
|     public static class MpmcAtomic { | ||||
|         public final Queue<Long> queue = new MpmcAtomicArrayQueue<>(CAPACITY); | ||||
|     } | ||||
| 
 | ||||
|     @State(Scope.Group) | ||||
|     public static class Jdk { | ||||
|         public final Queue<Long> queue = new ArrayBlockingQueue<>(CAPACITY); | ||||
|     } | ||||
| 
 | ||||
|     @Benchmark | ||||
|     @Group(GROUP_UNSAFE) | ||||
|     @GroupThreads(PRODUCER_THREADS_NUMBER) | ||||
|     public void mpmcWrite(Control control, Mpmc state) { | ||||
|         write(control, state.queue); | ||||
|     } | ||||
| 
 | ||||
|     @Benchmark | ||||
|     @Group(GROUP_UNSAFE) | ||||
|     @GroupThreads(CONSUMER_THREADS_NUMBER) | ||||
|     public void mpmcRead(Control control, Mpmc state) { | ||||
|         read(control, state.queue); | ||||
|     } | ||||
| 
 | ||||
|     @Benchmark | ||||
|     @Group(GROUP_AFU) | ||||
|     @GroupThreads(PRODUCER_THREADS_NUMBER) | ||||
|     public void mpmcAtomicWrite(Control control, MpmcAtomic state) { | ||||
|         write(control, state.queue); | ||||
|     } | ||||
| 
 | ||||
|     @Benchmark | ||||
|     @Group(GROUP_AFU) | ||||
|     @GroupThreads(CONSUMER_THREADS_NUMBER) | ||||
|     public void mpmcAtomicRead(Control control, MpmcAtomic state) { | ||||
|         read(control, state.queue); | ||||
|     } | ||||
| 
 | ||||
|     @Benchmark | ||||
|     @Group(GROUP_JDK) | ||||
|     @GroupThreads(PRODUCER_THREADS_NUMBER) | ||||
|     public void jdkWrite(Control control, Jdk state) { | ||||
|         write(control, state.queue); | ||||
|     } | ||||
| 
 | ||||
|     @Benchmark | ||||
|     @Group(GROUP_JDK) | ||||
|     @GroupThreads(CONSUMER_THREADS_NUMBER) | ||||
|     public void jdkRead(Control control, Jdk state) { | ||||
|         read(control, state.queue); | ||||
|     } | ||||
| 
 | ||||
|     private void write(Control control, Queue<Long> queue) { | ||||
|         //noinspection StatementWithEmptyBody | ||||
|         while (!control.stopMeasurement && !queue.offer(1L)) { | ||||
|             // Is intentionally left blank | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void read(Control control, Queue<Long> queue) { | ||||
|         //noinspection StatementWithEmptyBody | ||||
|         while (!control.stopMeasurement && queue.poll() == null) { | ||||
|             // Is intentionally left blank | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,86 @@ | ||||
| package com.baeldung.jctools; | ||||
| 
 | ||||
| import org.jctools.queues.SpscArrayQueue; | ||||
| import org.jctools.queues.SpscChunkedArrayQueue; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.CountDownLatch; | ||||
| import java.util.concurrent.atomic.AtomicReference; | ||||
| import java.util.function.IntConsumer; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.stream.IntStream; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.assertj.core.api.Assertions.fail; | ||||
| 
 | ||||
| public class JCToolsUnitTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenMultipleProducers_whenSpscQueueUsed_thenNoWarningOccurs() throws InterruptedException { | ||||
|         SpscArrayQueue<Integer> queue = new SpscArrayQueue<Integer>(2); | ||||
| 
 | ||||
|         Thread producer1 = new Thread(() -> { | ||||
|             queue.offer(1); | ||||
|         }); | ||||
|         producer1.start(); | ||||
|         producer1.join(); | ||||
| 
 | ||||
|         Thread producer2 = new Thread(() -> { | ||||
|             queue.offer(2); | ||||
|         }); | ||||
|         producer2.start(); | ||||
|         producer2.join(); | ||||
| 
 | ||||
|         Set<Integer> fromQueue = new HashSet<>(); | ||||
|         Thread consumer = new Thread(() -> queue.drain(fromQueue::add)); | ||||
|         consumer.start(); | ||||
|         consumer.join(); | ||||
| 
 | ||||
|         assertThat(fromQueue).containsOnly(1, 2); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenQueueIsFull_thenNoMoreElementsCanBeAdded() throws InterruptedException { | ||||
|         SpscChunkedArrayQueue<Integer> queue = new SpscChunkedArrayQueue<>(8, 16); | ||||
|         assertThat(queue.capacity()).isEqualTo(16); | ||||
| 
 | ||||
|         CountDownLatch startConsuming = new CountDownLatch(1); | ||||
|         CountDownLatch awakeProducer = new CountDownLatch(1); | ||||
|         AtomicReference<Throwable> error = new AtomicReference<>(); | ||||
|         Thread producer = new Thread(() -> { | ||||
|             IntStream.range(0, queue.capacity()).forEach(i -> { | ||||
|                 assertThat(queue.offer(i)).isTrue(); | ||||
|             }); | ||||
|             assertThat(queue.offer(queue.capacity())).isFalse(); | ||||
|             startConsuming.countDown(); | ||||
|             try { | ||||
|                 awakeProducer.await(); | ||||
|             } catch (InterruptedException e) { | ||||
|                 throw new RuntimeException(e); | ||||
|             } | ||||
| 
 | ||||
|             assertThat(queue.offer(queue.capacity())).isTrue(); | ||||
|         }); | ||||
|         producer.setUncaughtExceptionHandler((t, e) -> { | ||||
|             error.set(e); | ||||
|             startConsuming.countDown(); | ||||
|         }); | ||||
|         producer.start(); | ||||
| 
 | ||||
|         startConsuming.await(); | ||||
| 
 | ||||
|         if (error.get() != null) { | ||||
|             fail("Producer's assertion failed", error.get()); | ||||
|         } | ||||
| 
 | ||||
|         Set<Integer> fromQueue = new HashSet<>(); | ||||
|         queue.drain(fromQueue::add); | ||||
|         awakeProducer.countDown(); | ||||
|         producer.join(); | ||||
|         queue.drain(fromQueue::add); | ||||
| 
 | ||||
|         assertThat(fromQueue).containsAll(IntStream.range(0, 17).boxed().collect(Collectors.toSet())); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user