unsafe

本文由 ImportNew - fzr 翻译自 Peter Lawrey。欢迎加入翻译小组。转载请见文末要求。html

综述

sun.misc.Unsafe至少从2004年Java1.4开始就存在于Java中了。在Java9中,为了提升JVM的可维护性,Unsafe和许多其余的东西一块儿都被做为内部使用类隐藏起来了。可是到底是什么取代Unsafe不得而知,我的推测会有不止同样来取代它,那么问题来了,到底为何要使用Unsafe?java

作一些Java语言不容许可是又十分有用的事情

不少低级语言中可用的技巧在Java中都是不被容许的。对大多数开发者而言这是件好事,既能够拯救你,也能够拯救你的同事们。一样也使得导入开源代码更容易了,由于你能掌握它们能够形成的最大的灾难上限。或者至少明确你能够不当心失误的界限。若是你尝试地足够努力,你也能形成损害。算法

那你可能会奇怪,为何还要去尝试呢?当创建库时,Unsafe中不少(但不是全部)方法都颇有用,且有些状况下,除了使用JNI,没有其余方法作一样的事情,即便它可能会更加危险同时也会失去Java的“一次编译,永久运行”的跨平台特性。缓存

对象的反序列化

当使用框架反序列化或者构建对象时,会假设从已存在的对象中重建,你指望使用反射来调用类的设置函数,或者更准确一点是能直接设置内部字段甚至是final字段的函数。问题是你想建立一个对象的实例,但你实际上又不须要构造函数,由于它可能会使问题更加困难并且会有反作用。安全

1app

2框架

3dom

4ide

5函数

6

7

8

9

10

11

public class A implements Serializable {

    private final int num;

        public A(int num) {

        System.out.println("Hello Mum");

        this.num = num;

    }

 

    public int getNum() {

        return num;

    }

}

在这个类中,应该可以重建和设置final字段,但若是你不得不调用构造函数时,它就可能作一些和反序列化无关的事情。有了这些缘由,不少库使用Unsafe建立实例而不是调用构造函数。

1

2

3

Unsafe unsafe = getUnsafe();

Class aClass = A.class;

A a = (A) unsafe.allocateInstance(aClass);

调用allocateInstance函数避免了在咱们不须要构造函数的时候却调用它。

线程安全的直接获取内存

Unsafe的另一个用途是线程安全的获取非堆内存。ByteBuffer函数也能使你安全的获取非堆内存或是DirectMemory,但它不会提供任何线程安全的操做。你在进程间共享数据时使用Unsafe尤为有用。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

import sun.misc.Unsafe;

    import sun.nio.ch.DirectBuffer;

 

    import java.io.File;

    import java.io.IOException;

    import java.io.RandomAccessFile;

    import java.lang.reflect.Field;

    import java.nio.MappedByteBuffer;

    import java.nio.channels.FileChannel;

 

    public class PingPongMapMain {

        public static void main(String... args) throws IOException {

            boolean odd;

            switch (args.length < 1 ? "usage" : args[0].toLowerCase()) {

                case "odd":

                    odd = true;

                    break;

                case "even":

                    odd = false;

                    break;

                default:

                    System.err.println("Usage: java PingPongMain [odd|even]");

                    return;

            }

            int runs = 10000000;

            long start = 0;

            System.out.println("Waiting for the other odd/even");

            File counters = new File(System.getProperty("java.io.tmpdir"), "counters.deleteme");

            counters.deleteOnExit();

 

            try (FileChannel fc = new RandomAccessFile(counters, "rw").getChannel()) {

                MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

                long address = ((DirectBuffer) mbb).address();

                for (int i = -1; i < runs; i++) {

                    for (; ; ) {

                        long value = UNSAFE.getLongVolatile(null, address);

                        boolean isOdd = (value & 1) != 0;

                        if (isOdd != odd)

// wait for the other side.

                            continue;

// make the change atomic, just in case there is more than one odd/even process

                        if (UNSAFE.compareAndSwapLong(null, address, value, value + 1))

                            break;

                    }

                    if (i == 0) {

                        System.out.println("Started");

                        start = System.nanoTime();

                    }

                }

            }

            System.out.printf("... Finished, average ping/pong took %,d ns%n",

                    (System.nanoTime() - start) / runs);

        }

 

        static final Unsafe UNSAFE;

 

        static {

            try {

                Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");

                theUnsafe.setAccessible(true);

                UNSAFE = (Unsafe) theUnsafe.get(null);

            } catch (Exception e) {

                throw new AssertionError(e);

            }

        }

    }

当你分别在两个程序,一个输入odd一个输入even,中运行时,能够看到两个进程都是经过持久化共享内存交换数据的。

在每一个程序中,将相同的磁盘缓存映射到进程中。内存中实际上只有一份文件的副本存在。这意味着内存能够共享,前提是你使用线程安全的操做,好比volatile变量和CAS操做。(译注:CAS Compare and Swap 无锁算法)

在两个进程之间有83ns的往返时间。当考虑到System V IPC(进程间通讯)大约须要2500ns,并且用IPC volatile替代persisted内存,算是至关快的了。

Unsafe适合在工做中使用吗?

我的不建议直接使用Unsafe。它远比原生的Java开发所须要的测试多。基于这个缘由建议仍是使用通过测试的库。若是你只是想本身用Unsafe,建议你最好在一个独立的类库中进行全面的测试。这限制了Unsafe在你的应用程序中的使用方式,但会给你一个更安全的Unsafe。

总结

Unsafe在Java中是颇有趣的一个存在,你能够一我的在家里随便玩玩。它也有一些工做的应用程序特别是在写底层库的时候,但总的来讲,使用通过测试的Unsafe库比直接用要好。

原文连接: Peter Lawrey 翻译: ImportNew.com fzr
译文连接: http://www.importnew.com/14511.html
转载请保留原文出处、译者和译文连接。]

相关文章
相关标签/搜索