摘要:坑一慎用方法在類中,有一個方法是,返回的是一個數(shù)組,該數(shù)組包含了所包含的方法??佣饔镁€程優(yōu)先級做并發(fā)處理線程中有屬性,表示線程的優(yōu)先級,默認值為,取值區(qū)間為。顯然,運行時環(huán)境是因操作系統(tǒng)而異的。
本文為作者原創(chuàng),轉(zhuǎn)載請注明出處。
我們都知道Java是跨平臺的,一次編譯,到處運行,本質(zhì)上依賴于不同操作系統(tǒng)下有不同的JVM。到處運行是做到了,但運行結(jié)果呢?一樣的程序,在不同的JVM上跑的結(jié)果是否一樣呢?很遺憾,程序的執(zhí)行結(jié)果沒有百分百的確定性,本篇分享我遇到的一些case。
坑一 慎用Class.getMethods()方法在Class類中,有一個方法是getMethods(),返回的是一個Method數(shù)組,該數(shù)組包含了Class所包含的方法。但是需要注意的是,其數(shù)組元素的排序是不確定的,在不同的機器上會有不一樣的排序輸出。
public Method[] getMethods() throws SecurityException { checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); return copyMethods(privateGetPublicMethods()); }
阿里的fastjson就曾經(jīng)在這里踩到坑了,fastjson是序列化框架,當要去獲取對象的某個屬性值時,往往需要通過反射調(diào)用getter方法。比如,有個屬性field,那么通過遍歷Method數(shù)組,判斷是否有g(shù)etField方法,如果有的話,則調(diào)用取得相應(yīng)的值。
但對于boolean類型的字段,其getter方法有可能是isXXX,也有可能是getXXX,而fastjson在遍歷時,只要判斷有isXXX或者getXXX,就認定其為getter方法,然后立即執(zhí)行該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到底執(zhí)行的是isA()還是getA()呢?答案是不確定,因為isA和getA在返回的Method數(shù)組中順序是不確定的,所以有的機器上可能是通過isA()來獲取屬性值,有的機器上可能是通過getA()來獲取屬性值,而這兩個方法返回的一個是boolean類型,一個是A類型,導致fastjson在不同機器執(zhí)行的結(jié)果是不一樣的。
為什么這個方法返回值不按照字母排序呢?每個類或者方法名字都會對應(yīng)一個Symbol對象,在這個名字第一次使用的時候構(gòu)建,Symbol對象是通過malloc來分配的,因此新分配的Symbol對象的地址就不一定比后分配的Symbol對象地址小,也不一定大,因為期間存在內(nèi)存free的動作,那地址是不會一直線性變化的,之所以不按照字母排序,主要還是為了速度考慮,根據(jù)Symbol對象的地址排序是最快的。
坑二 慎用線程優(yōu)先級做并發(fā)處理線程Thread中有priority屬性,表示線程的優(yōu)先級,默認值為5,取值區(qū)間為[1,10]。雖然在Thread的注釋中有說明優(yōu)先級高的線程將會被優(yōu)先執(zhí)行,但是測試結(jié)果,卻是隨機的。
如下,
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 線程優(yōu)先級設(shè)置為10 t2.setPriority(5); // t2 線程優(yōu)先級設(shè)置為5 t3.setPriority(1); // t3 線程優(yōu)先級設(shè)置為1 t1.start(); t2.start(); t3.start(); }
如果是嚴格按照線程優(yōu)先級來執(zhí)行的,那么應(yīng)該是t1執(zhí)行for循環(huán),然后t2執(zhí)行完for循環(huán),最后t3執(zhí)行for循環(huán)。但實際上測試結(jié)果顯示,每次執(zhí)行的輸出順序都沒有遵循這個規(guī)則,并且每次執(zhí)行的結(jié)果都是不一樣的。
---- console output ---- thread-2---0 thread-2---1 thread-3---0 thread-1---0 thread-1---1 thread-1---2 thread-3---1 ...... ......
線程調(diào)度具有很多不確定性,線程的優(yōu)先級只是對線程的一個標志,但不代表著這是絕對的優(yōu)先,具體的執(zhí)行順序都是由操作系統(tǒng)本身的資源調(diào)度來決定的。不同操作系統(tǒng)本身的線程調(diào)度方式可能存在差異性,所以不能依靠線程優(yōu)先級來處理并發(fā)邏輯。
坑三 慎用系統(tǒng)時間做精確時間計算Java API中,一般使用native方法System.currentTimeMillis() 來獲取系統(tǒng)的時間。從方法名上,可以看出,該方法用于獲取系統(tǒng)當前的時間,即從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(); }
方法注釋明確指出了這個毫秒值的精度在不同的操作系統(tǒng)中是存在差異的,有的系統(tǒng)1毫秒實際上等同于物理時間的幾十毫秒。也就是說,在一個性能測試中,因為精度不一致的問題,有的系統(tǒng)得出的結(jié)果是1毫秒,另外系統(tǒng)得出的性能結(jié)果卻是10毫秒。
那如何實現(xiàn)高精度的時間計算呢?先來看看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(); }
這個方法只能用于檢測系統(tǒng)經(jīng)過的時間,也就是說其返回的時間不是從1970年1月1日8時開始的納秒時間,是從系統(tǒng)啟動開始時開始計算的時間。
所以一般高精度的時間是采用System.nanoTime()方法來實現(xiàn)的,其單位為納秒(十億分之一秒),雖然不保證完全準確的納秒級精度。但用該方法來實現(xiàn)毫秒級精度的計算,是綽綽有余的,如下。
long start = System.nanoTime(); // do something long end = System.nanoTime(); // 程序執(zhí)行的時間,精確到毫秒 long costTime = (end - start) / 1000000L坑四 慎用運行時Runtime類
Runtime是JVM中運行時環(huán)境的抽象,包含了運行時環(huán)境的一些信息,每個Java應(yīng)用程序都有一個Runtime實例,用于應(yīng)用程序和其所在的運行時環(huán)境進行交互。應(yīng)用程序本身無法創(chuàng)建Runtime實例,只能通過Runtime.getRuntime()方法來獲取。
顯然,運行時環(huán)境是因操作系統(tǒng)而異的。其交互方式也存在差異,
例如,
// Windows下調(diào)用程序 Process proc =Runtime.getRuntime().exec("exefile"); // Linux下調(diào)用程序 Process proc =Runtime.getRuntime().exec("./exefile");
所以,如果應(yīng)用程序中包含這類和運行時環(huán)境進行交互的方法,應(yīng)確保應(yīng)用的部署環(huán)境不變,如果不能保證的話,那么至少需要提供兩套運行時交互邏輯。
以上是我遇到的不能跨平臺的一些case,其實本質(zhì)上都和native實現(xiàn)有關(guān)。你有沒有遇到一些這樣的坑呢?歡迎留言~
參考鏈接:
JVM源碼分析之不保證順序的Class.getMethods
公眾號簡介:作者是螞蟻金服的一線開發(fā),分享自己的成長和思考之路。內(nèi)容涉及數(shù)據(jù)、工程、算法。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/76221.html
摘要:本章中的大部分內(nèi)容適用于構(gòu)造函數(shù)和方法。第項其他方法優(yōu)先于序列化第項謹慎地實現(xiàn)接口第項考慮使用自定義的序列化形式第項保護性地編寫方法第項對于實例控制,枚舉類型優(yōu)先于第項考慮用序列化代理代替序列化實例附錄與第版中項目的對應(yīng)關(guān)系參考文獻 effective-java-third-edition 介紹 Effective Java 第三版全文翻譯,純屬個人業(yè)余翻譯,不合理的地方,望指正,感激...
摘要:正在學習,留著看看轉(zhuǎn)自的大坑小洼成為云計算領(lǐng)域的新寵兒已經(jīng)是不爭的事實,作為高速發(fā)展的開源項目,難免存在這樣或那樣的瑕疵。話不多說,一起來領(lǐng)略的大坑小洼。原因回歸至上文的第一個坑。如此一來,只要內(nèi)部涉及到域名解析,則立即受到影響。 正在學習Docker,留著看看 轉(zhuǎn)自Docker的大坑小洼 Docker成為云計算領(lǐng)域的新寵兒已經(jīng)是不爭的事實,作為高速發(fā)展的開源項目,難免存在這樣或那樣...
摘要:比特幣和以太幣屬于一類區(qū)塊鏈,我們將其歸類為公共無許可的區(qū)塊鏈技術(shù)。例如,在單個企業(yè)中部署時,或由受信任的權(quán)威機構(gòu)運作,完全拜占庭容錯的共識可能被認為是不必要的,并且對性能和吞吐量造成過度的拖累。 介紹 一般而言,區(qū)塊鏈是一個不可變的交易分類賬,維護在一個分布式對等節(jié)點網(wǎng)絡(luò)中。這些節(jié)點通過應(yīng)用已經(jīng)由共識協(xié)議驗證的交易來維護分類帳的副本,該交易被分組為包括將每個塊綁定到前一個塊的散列的塊...
閱讀 3084·2021-11-24 10:32
閱讀 753·2021-11-24 10:19
閱讀 5517·2021-08-11 11:17
閱讀 1527·2019-08-26 13:31
閱讀 1317·2019-08-23 15:15
閱讀 2338·2019-08-23 14:46
閱讀 2350·2019-08-23 14:07
閱讀 1188·2019-08-23 14:03