From 5e0d1e88ef20cb7965a726bed4a4f9b567b68a4f Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Sat, 15 Apr 2017 17:34:56 +0200 Subject: [PATCH] BAEL-814 unsafe (#1645) * code for the unsafe article * more descriptive example * proper eng * better test name * free memory call * java 8 style --- .../java/com/baeldung/unsafe/CASCounter.java | 33 +++++ .../com/baeldung/unsafe/OffHeapArray.java | 39 ++++++ .../java/com/baeldung/unsafe/UnsafeTest.java | 118 ++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 core-java/src/test/java/com/baeldung/unsafe/CASCounter.java create mode 100644 core-java/src/test/java/com/baeldung/unsafe/OffHeapArray.java create mode 100644 core-java/src/test/java/com/baeldung/unsafe/UnsafeTest.java diff --git a/core-java/src/test/java/com/baeldung/unsafe/CASCounter.java b/core-java/src/test/java/com/baeldung/unsafe/CASCounter.java new file mode 100644 index 0000000000..f7f3b340c2 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/unsafe/CASCounter.java @@ -0,0 +1,33 @@ +package com.baeldung.unsafe; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +class CASCounter { + private final Unsafe unsafe; + private volatile long counter = 0; + private long offset; + + private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get(null); + } + + public CASCounter() throws Exception { + unsafe = getUnsafe(); + offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter")); + } + + public void increment() { + long before = counter; + while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) { + before = counter; + } + } + + public long getCounter() { + return counter; + } +} \ No newline at end of file diff --git a/core-java/src/test/java/com/baeldung/unsafe/OffHeapArray.java b/core-java/src/test/java/com/baeldung/unsafe/OffHeapArray.java new file mode 100644 index 0000000000..f5cab88f3d --- /dev/null +++ b/core-java/src/test/java/com/baeldung/unsafe/OffHeapArray.java @@ -0,0 +1,39 @@ +package com.baeldung.unsafe; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +class OffHeapArray { + private final static int BYTE = 1; + private long size; + private long address; + + private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get(null); + } + + public OffHeapArray(long size) throws NoSuchFieldException, IllegalAccessException { + this.size = size; + address = getUnsafe().allocateMemory(size * BYTE); + } + + public void set(long i, byte value) throws NoSuchFieldException, IllegalAccessException { + getUnsafe().putByte(address + i * BYTE, value); + } + + public int get(long idx) throws NoSuchFieldException, IllegalAccessException { + return getUnsafe().getByte(address + idx * BYTE); + } + + public long size() { + return size; + } + + public void freeMemory() throws NoSuchFieldException, IllegalAccessException { + getUnsafe().freeMemory(address); + } + +} diff --git a/core-java/src/test/java/com/baeldung/unsafe/UnsafeTest.java b/core-java/src/test/java/com/baeldung/unsafe/UnsafeTest.java new file mode 100644 index 0000000000..6aa4973ee7 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/unsafe/UnsafeTest.java @@ -0,0 +1,118 @@ +package com.baeldung.unsafe; + +import org.junit.Before; +import org.junit.Test; +import sun.misc.Unsafe; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertEquals; + +public class UnsafeTest { + + private Unsafe unsafe; + + @Before + public void setup() throws NoSuchFieldException, IllegalAccessException { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } + + + @Test + public void givenClass_whenInitializeIt_thenShouldHaveDifferentStateWhenUseUnsafe() throws IllegalAccessException, InstantiationException { + //when + InitializationOrdering o1 = new InitializationOrdering(); + assertEquals(o1.getA(), 1); + + //when + InitializationOrdering o3 = (InitializationOrdering) unsafe.allocateInstance(InitializationOrdering.class); + assertEquals(o3.getA(), 0); + } + + @Test + public void givenPrivateMethod_whenUsingUnsafe_thenCanModifyPrivateField() throws NoSuchFieldException { + //given + SecretHolder secretHolder = new SecretHolder(); + + //when + Field f = secretHolder.getClass().getDeclaredField("SECRET_VALUE"); + unsafe.putInt(secretHolder, unsafe.objectFieldOffset(f), 1); + + //then + assertTrue(secretHolder.secretIsDisclosed()); + } + + @Test(expected = IOException.class) + public void givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt() { + unsafe.throwException(new IOException()); + } + + @Test + public void givenArrayBiggerThatMaxInt_whenAllocateItOffHeapMemory_thenSuccess() throws NoSuchFieldException, IllegalAccessException { + //given + long SUPER_SIZE = (long) Integer.MAX_VALUE * 2; + OffHeapArray array = new OffHeapArray(SUPER_SIZE); + + //when + int sum = 0; + for (int i = 0; i < 100; i++) { + array.set((long) Integer.MAX_VALUE + i, (byte) 3); + sum += array.get((long) Integer.MAX_VALUE + i); + } + + //then + assertEquals(array.size(), SUPER_SIZE); + assertEquals(sum, 300); + } + + @Test + public void givenUnsafeCompareAndSwap_whenUseIt_thenCounterYildCorrectLockFreeResults() throws Exception { + //given + int NUM_OF_THREADS = 1_000; + int NUM_OF_INCREMENTS = 10_000; + ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS); + CASCounter casCounter = new CASCounter(); + + //when + IntStream.rangeClosed(0, NUM_OF_THREADS - 1) + .forEach(i -> service.submit(() -> IntStream + .rangeClosed(0, NUM_OF_INCREMENTS - 1) + .forEach(j -> casCounter.increment()))); + + service.shutdown(); + service.awaitTermination(1, TimeUnit.MINUTES); + + //then + assertEquals(NUM_OF_INCREMENTS * NUM_OF_THREADS, casCounter.getCounter()); + + } + + class InitializationOrdering { + private long a; + + public InitializationOrdering() { + this.a = 1; + } + + public long getA() { + return this.a; + } + } + + class SecretHolder { + private int SECRET_VALUE = 0; + + public boolean secretIsDisclosed() { + return SECRET_VALUE == 1; + } + } + +}