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
轉載請保留原文出處、譯者和譯文連接。]

相關文章
相關標籤/搜索