Java最高

开始使用Spring 5和Spring Boot 2,通过学习的春天课程:

>>看看这个课程

1.概述

在本文中,我们将看看由JRE -提供的一个迷人的类不安全的sun.misc包中。这个类为我们提供了仅供核心Java库使用而非标准用户使用的低级机制。

这为我们提供了主要设计用于核心库内部使用的低级机制。

2.对象的实例获取不安全的

首先,要能够使用不安全的类,我们需要获得一个实例——这并不简单,因为类是专为内部使用而设计的。

获取实例的方法是通过静态方法getUnsafe()。需要注意的是,默认情况下—这将抛出SecurityException。

幸运的是,我们可以使用反射来获得实例:

字段f =不安全的。class. getdeclaredfield(“不安全的”);f.setAccessible(真正的);不安全=(不安全)f.t get(null);

3.实例化一个类使用不安全的

假设我们有一个简单的类,其中一个构造函数在创建对象时设置变量值:

class initializationordered {private long a;public initializationordered(){这个。= 1;} public long getA() {return this.a;}}

当我们使用构造函数初始化该对象时,木屐()方法将返回值为1:

initializationordero1 = new initializationorder();assertequal (o1.getA (), 1);

但是我们可以用allocateInstance ()方法使用不安全的。它只会为我们的类分配内存,而不会调用构造函数:

initializationordero3 = (initializationordero .class)不安全;assertequal (o3.getA (), 0);

注意,构造函数没有被调用,因此木屐()方法返回的默认值Type -是0。

4.改变私有字段

假设我们有一个保存a的类秘密私人价值:

class SecretHolder {private int SECRET_VALUE = 0;public boolean secretis披露(){返回SECRET_VALUE == 1;}}

使用putInt ()方法从不安全,我们可以改变private的一个值SECRET_VALUE字段,更改/破坏该实例的状态:

SecretHolder = new SecretHolder();字段f = secrethholder . getclass ().getDeclaredField(“SECRET_VALUE”);不安全的。putInt (secretHolder unsafe.objectFieldOffset (f), 1);assertTrue (secretHolder.secretIsDisclosed ());

一旦我们通过反射调用获得了一个字段,我们就可以将它的值更改为任何其他值使用价值不安全的

5.抛出异常

通过调用的代码不安全的编译器不会以与常规Java代码相同的方式检查。我们可以使用throwException ()方法抛出任何异常,而不限制调用方处理该异常,即使它是已检查异常:

@Test(expected = IOException.class) public void givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt(){不安全。throwException(新IOException ());}

后抛出IOException,我们不需要捕获它,也不需要在方法声明中指定它。

6.堆内存

如果应用程序耗尽了JVM上的可用内存,我们最终可能会迫使GC进程过于频繁地运行。理想情况下,我们希望有一个特殊的内存区域,不堆,不受GC进程控制。

allocateMemory ()从中的方法不安全的类使我们能够从堆中分配大型对象,这意味着这个内存不会被GC和JVM看到和考虑

这可能非常有用,但我们需要记住,需要手动管理这些内存,并正确地回收这些内存freeMemory ()当不再需要的时候。

假设我们想要创建一个大的非堆内存字节数组。我们可以使用allocateMemory ()实现方法:

class OffHeapArray {private final static BYTE = 1;私人长尺寸;私人长地址;public OffHeapArray(long size)抛出NoSuchFieldException, IllegalAccessException {this。大小=大小;地址= getUnsafe()。allocateMemory(*字节大小);} private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class. getdeclaredfield("不安全的");f.setAccessible(真正的);返回(不安全)f.get(空);} 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); }
}

的构造函数中OffHeapArray,我们在初始化给定的数组大小。数组的起始地址存储在地址字段。的设置()方法取索引和给定价值它将存储在数组中。的get ()方法使用其索引检索字节值,该索引是相对于数组起始地址的偏移量。

接下来,我们可以使用非堆数组的构造函数来分配这个数组:

long SUPER_SIZE = (long)整数。MAX_VALUE * 2;offhearray array = new offhearray (SUPER_SIZE);

我们可以将N个字节值放入这个数组中,然后检索这些值,将它们相加,以测试我们的寻址是否正确:

Int sum = 0;For (int I = 0;我< 100;i++) {array.set((long) Integer. i++)MAX_VALUE + i,(字节)3);sum += array.get((long) Integer。MAX_VALUE + i);} assertequal (array.size (), SUPER_SIZE);assertequal(和,300);

最后,我们需要通过调用将内存释放回操作系统freeMemory()。

7.CompareAndSwap手术

非常高效的结构来自java.concurrent包,就像AtomicInteger,使用的是compareAndSwap ()方法的不安全的在下面,提供最佳性能。该构建体广泛用于无锁无锁定算法,可以利用CAS处理器指令,与Java中的标准悲观同步机制相比提供了巨大的加速。

属性来构造基于CAS的计数器compareAndSwapLong ()方法从不安全的:

class CASCounter{私有不安全不安全;私有可变长计数器= 0;私人长抵消;private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class. getdeclaredfield ("theUnsafe");f.setAccessible(真正的);返回(不安全)f.get(空);} public CASCounter()抛出异常{不安全= getUnsafe();抵消= unsafe.objectFieldOffset (CASCounter.class.getDeclaredField(“计数器”));} public void increment() {long before = counter;while (!unsafe.compareAndSwapLong(this, offset, before, before)) {before = counter; } } public long getCounter() { return counter; } }

CASCounter构造函数中获取计数器字段的地址,以便以后在增加()方法。该字段需要声明为volatile,以便对所有正在写和读该值的线程可见。我们正在使用objectFieldOffset ()方法的内存地址抵消字段。

这门课最重要的部分是增加()方法。我们使用compareAndSwapLong ()循环递增之前获取的值,检查之前的值是否在我们获取它之后发生了变化。

如果是的话,我们会继续尝试直到成功。这里没有阻塞,这就是为什么它被称为无锁算法。

我们可以通过从多个线程递增共享计数器来测试我们的代码:

int NUM_OF_THREADS = 1_000;int NUM_OF_INCREMENTS = 10_000;ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);CASCounter = new CASCounter();IntStream。rangecclosed (0, NUM_OF_THREADS - 1) .forEach(i -> service.submit(() -> IntStream . rangecclosed (0, NUM_OF_INCREMENTS - 1) .forEach(j -> casCounter.increment())));

接下来,要断言计数器的状态是正确的,我们可以从中获得计数器的值:

assertEquals(NUM_OF_INCREMENTS * NUM_OF_THREADS, casCounter.getCounter());

8.公园/ Unpark

有两种有趣的方法不安全的JVM用于上下文切换线程的API。当线程等待某些操作时,JVM可以通过使用公园()从中的方法不安全的类。

这和Object.wait ()方法,但它调用本机操作系统代码,因此利用一些架构细节来获得最佳性能。

当线程被阻塞并需要重新使其可运行时,JVM将使用Unpark()方法。我们经常会看到线程转储中的那些方法调用,尤其是在使用线程池的应用程序中。

9.结论

在本文中,我们正在研究不安全的类及其最有用的构造。

我们了解了如何访问私有字段,如何分配堆外内存,以及如何使用比较和交换结构来实现无锁算法。

可以找到所有这些示例和代码片段的实现在GitHub-这是一个Maven项目,所以它应该很容易导入和运行。

Java底部

开始使用Spring 5和Spring Boot 2,通过学习的春天课程:

>>看看这个课程
2注释
最古老的
最新的
内联反馈
查看所有评论
对这篇文章的评论关闭!