本文爲做者原創,轉載請註明出處。
咱們都知道Java是跨平臺的,一次編譯,處處運行,本質上依賴於不一樣操做系統下有不一樣的JVM。處處運行是作到了,但運行結果呢?同樣的程序,在不一樣的JVM上跑的結果是否同樣呢?很遺憾,程序的執行結果沒有百分百的肯定性,本篇分享我遇到的一些case。java
在Class類中,有一個方法是getMethods(),返回的是一個Method數組,該數組包含了Class所包含的方法。可是須要注意的是,其數組元素的排序是不肯定的,在不一樣的機器上會有不同的排序輸出。算法
public Method[] getMethods() throws SecurityException { checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); return copyMethods(privateGetPublicMethods()); }
阿里的fastjson就曾經在這裏踩到坑了,fastjson是序列化框架,當要去獲取對象的某個屬性值時,每每須要經過反射調用getter方法。好比,有個屬性field,那麼經過遍歷Method數組,判斷是否有getField方法,若是有的話,則調用取得相應的值。json
但對於boolean類型的字段,其getter方法有多是isXXX,也有多是getXXX,而fastjson在遍歷時,只要判斷有isXXX或者getXXX,就認定其爲getter方法,而後當即執行該getter方法。數組
// 僞代碼 for (Method method : someObject.class.getMethods()) { // 判斷是否爲getter方法 if(method.getName().equals("getField") || method.getName().equals("isField")){ // 經過getter取得屬性值 return method.invoke(xxx, xxxx); } }
可是若是一個對象同時存在isA和getA方法呢?併發
private A a; private boolan isA(){ return false; } private A getA(){ return a; }
這個時候fastjson到底執行的是isA()仍是getA()呢?答案是不肯定,由於isA和getA在返回的Method數組中順序是不肯定的,因此有的機器上多是經過isA()來獲取屬性值,有的機器上多是經過getA()來獲取屬性值,而這兩個方法返回的一個是boolean類型,一個是A類型,致使fastjson在不一樣機器執行的結果是不同的。框架
爲何這個方法返回值不按照字母排序呢?每一個類或者方法名字都會對應一個Symbol對象,在這個名字第一次使用的時候構建,Symbol對象是經過malloc來分配的,所以新分配的Symbol對象的地址就不必定比後分配的Symbol對象地址小,也不必定大,由於期間存在內存free的動做,那地址是不會一直線性變化的,之因此不按照字母排序,主要仍是爲了速度考慮,根據Symbol對象的地址排序是最快的。ide
線程Thread中有priority屬性,表示線程的優先級,默認值爲5,取值區間爲[1,10]。雖然在Thread的註釋中有說明優先級高的線程將會被優先執行,可是測試結果,倒是隨機的。源碼分析
以下,性能
static class Runner implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); } } } public static void main(String[] args) { Thread t1 = new Thread(new Runner(), "thread-1"); Thread t2 = new Thread(new Runner(), "thread-2"); Thread t3 = new Thread(new Runner(), "thread-3"); t1.setPriority(10); // t1 線程優先級設置爲10 t2.setPriority(5); // t2 線程優先級設置爲5 t3.setPriority(1); // t3 線程優先級設置爲1 t1.start(); t2.start(); t3.start(); }
若是是嚴格按照線程優先級來執行的,那麼應該是t1執行for循環,而後t2執行完for循環,最後t3執行for循環。但實際上測試結果顯示,每次執行的輸出順序都沒有遵循這個規則,而且每次執行的結果都是不同的。測試
---- console output ---- thread-2---0 thread-2---1 thread-3---0 thread-1---0 thread-1---1 thread-1---2 thread-3---1 ...... ......
線程調度具備不少不肯定性,線程的優先級只是對線程的一個標誌,但不表明着這是絕對的優先,具體的執行順序都是由操做系統自己的資源調度來決定的。不一樣操做系統自己的線程調度方式可能存在差別性,因此不能依靠線程優先級來處理併發邏輯。
Java API中,通常使用native方法System.currentTimeMillis() 來獲取系統的時間。從方法名上,能夠看出,該方法用於獲取系統當前的時間,即從1970年1月1日8時到當前的毫秒值。
下面羅列出了官方對該方法的註釋:
public final class System { /** * Note that while the unit of time of the return value is a millisecond, * the granularity of the value depends on the underlying * operating system and may be larger. For example, many * operating systems measure time in units of tens of * milliseconds. */ public static native long currentTimeMillis(); }
方法註釋明確指出了這個毫秒值的精度在不一樣的操做系統中是存在差別的,有的系統1毫秒實際上等同於物理時間的幾十毫秒。也就是說,在一個性能測試中,由於精度不一致的問題,有的系統得出的結果是1毫秒,另外系統得出的性能結果倒是10毫秒。
那如何實現高精度的時間計算呢?先來看看System.nanoTime()方法,下面列出了官方的核心註釋:
public final class System { /** * This method can only be used to measure elapsed time and is * not related to any other notion of system or wall-clock time. */ public static native long nanoTime(); }
這個方法只能用於檢測系統通過的時間,也就是說其返回的時間不是從1970年1月1日8時開始的納秒時間,是從系統啓動開始時開始計算的時間。
因此通常高精度的時間是採用System.nanoTime()方法來實現的,其單位爲納秒(十億分之一秒),雖然不保證徹底準確的納秒級精度。但用該方法來實現毫秒級精度的計算,是綽綽有餘的,以下。
long start = System.nanoTime(); // do something long end = System.nanoTime(); // 程序執行的時間,精確到毫秒 long costTime = (end - start) / 1000000L
Runtime是JVM中運行時環境的抽象,包含了運行時環境的一些信息,每一個Java應用程序都有一個Runtime實例,用於應用程序和其所在的運行時環境進行交互。應用程序自己沒法建立Runtime實例,只能經過Runtime.getRuntime()方法來獲取。
顯然,運行時環境是因操做系統而異的。其交互方式也存在差別,
例如,
// Windows下調用程序 Process proc =Runtime.getRuntime().exec("exefile"); // Linux下調用程序 Process proc =Runtime.getRuntime().exec("./exefile");
因此,若是應用程序中包含這類和運行時環境進行交互的方法,應確保應用的部署環境不變,若是不能保證的話,那麼至少須要提供兩套運行時交互邏輯。
以上是我遇到的不能跨平臺的一些case,其實本質上都和native實現有關。你有沒有遇到一些這樣的坑呢?歡迎留言~
參考連接:
JVM源碼分析之不保證順序的Class.getMethods
公衆號簡介:做者是螞蟻金服的一線開發,分享本身的成長和思考之路。內容涉及數據、工程、算法。