Java為什么會引入及如何使用Unsafe

jopen 9年前發布 | 12K 次閱讀 Java

綜述

sun.misc.Unsafe至少從2004年Java1.4開始就存在于Java中了。在Java9中,為了提高JVM的可維護性,Unsafe和許多其他的東西一起都被作為內部使用類隱藏起來了。但是究竟是什么取代Unsafe不得而知,個人推測會有不止一樣來取代它,那么問題來了,到底為什么要使用Unsafe?

做一些Java語言不允許但是又十分有用的事情

很多低級語言中可用的技巧在Java中都是不被允許的。對大多數開發者而言這是件好事,既可以拯救你,也可以拯救你的同事們。同樣也使得導入開源代碼更容易了,因為你能掌握它們可以造成的最大的災難上限。或者至少明確你可以不小心失誤的界限。如果你嘗試地足夠努力,你也能造成損害。

那你可能會奇怪,為什么還要去嘗試呢?當建立庫時,Unsafe中很多(但不是所有)方法都很有用,且有些情況下,除了使用JNI,沒有其他方法做同樣的事情,即使它可能會更加危險同時也會失去Java的“一次編譯,永久運行”的跨平臺特性。

對象的反序列化

當使用框架反序列化或者構建對象時,會假設從已存在的對象中重建,你期望使用反射來調用類的設置函數,或者更準確一點是能直接設置內部字段甚至是final字段的函數。問題是你想創建一個對象的實例,但你實際上又不需要構造函數,因為它可能會使問題更加困難而且會有副作用。

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創建實例而不是調用構造函數。

Unsafe unsafe = getUnsafe();
Class aClass = A.class;
A a = (A) unsafe.allocateInstance(aClass);

調用allocateInstance函數避免了在我們不需要構造函數的時候卻調用它。

線程安全的直接獲取內存

Unsafe的另外一個用途是線程安全的獲取非堆內存。ByteBuffer函數也能使你安全的獲取非堆內存或是DirectMemory,但它不會提供任何線程安全的操作。你在進程間共享數據時使用Unsafe尤其有用。

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 - 范 忠瑞
譯文鏈接: http://www.importnew.com/14511.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!