Java中Integer和ThreadLocal
一. Integer
1.引子
在開始之前,我還是需要吐槽下自己,我是真的很菜!
- 他問:**兩個Integer對象==比較是否相等?
- 我答:對象==比較,是引用比較,不相等!
- 他問:IntegerCache這個用來幹什麽?
- 我答:......,我不是很清楚!!!
從這裏可以看出,我是真的很水!!
基於這些原因還有其他的等等,我開始寫博文,開始寫踩過的坑!
2.IntegerCache
在介紹這個緩存之前,先來認知下Integer。java中,int是基本的數值類型,表示整型;Integer是其對象的表示,是int的包裝類。包裝類在Jdk1中就存在,但是為了更方便的使用Integer,在Java SE5中引入自動包裝和自動拆箱的機制,這裏不再贅述,可以移步至深入剖析Java中的裝箱和拆箱。
Java SE5可以說是java版本叠代過程中裏程碑版本。為了減少大量創建Integer的開銷、提高性能,采用享元模式,引入Integer實例緩存,將-128至127範圍數值的Integer實例緩存在內存中
先看下如何獲取緩存的-128至127範圍的Integer實例,這裏的範圍可以通過JVM啟動參數修改,後面會講述
- 可以通過使用靜態valueOf方法獲取 // api調用
- 可以使用自動包裝機制獲取,如:
Integer a = 100;
// autoboxing
這裏基於Java SE8,再來看下IntegerCache的實現:
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
從java docs中可以看出,該類是為了支持對-128至127範圍數值自動包裝的實例進行緩存。
在第一次使用時緩存被初始化,緩存的大小可以通過JVM參數-XX:AutoBoxCacheMax=size
控制,IntegerCache的high可以通過在JVM啟動時設置系統屬性變量java.lang.Integer.IntegerCache.high
,以達到更強的靈活性。IntegerCache在執行類構造器初始化時,執行靜態語句塊,利用for循環從low開始不斷自加加,然後放入cache數組中。
選擇-128至127範圍,是因為這個範圍內的數值使用更普遍!
介紹到這裏,相信已經不難看出我的回答是多麽搞笑,我坐井觀天的還以為自己是正確的!
說到這裏,我又有些感慨了,昨晚看了集火影,剛好是自來也和佩恩的對話,佩恩對曾經的師傅自來也的一席話,雖然是偏執的,但是無可否認的確是對的。
佩恩把自己稱為神,將自來也視作為凡人,說凡人的認知具有局限性,活在狹小的空間內自認為自己認識都是對的,殊不知從神的角度考慮,凡人的那些認知都是錯誤和愚蠢的。
這裏旨在單純思考佩恩這些話在我們現實生活中的含義:的確,在我們認知有限的時候,以為Integer兩個對象==比較,應該是不等的,殊不知因為技術掌握有限。
跑題了,引入一段故事,主要是想銘刻下,吸取教訓:不要把事物看做是理所當然,要多多思考其本質和原理;不要妄下結論,小心求證!
不僅是Integer,Short、Byte、Long、Char都具有相應的Cache。但是Byte、Short、Long的Cache有固定範圍:-128至127;Char的Cache:0至127。
引用
理解Java Integer的緩存策略
二. ThreadLocal
我這裏不再贅述ThreadLocal是什麽、解決什麽問題、實現原理、應用場景等,網上博文非常豐富,可移步至深入剖析ThreadLocal。
這裏主要是分析下ThreadLocal在父子線程中共享的問題。
ThreadLocal不提供對子線程共享父線程的ThreadLocal中存儲的value的支持,ThreadLocal的get方法返回與當前線程有關的ThreadLocalMap中的key/value,如下圖:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
每個線程在創建的時候,都會初始化ThreadLocalMap的空引用。 子線程在創建時ThreadLocaMap是null
,所以不共享父線程的ThreadLocalMap中key/value。
但是有很多時候,實際需求需要共享父線程的一些狀態值。 這種情況,java也對此提供了支持,只能說設計者們的腦洞大開。
java中引入另一個InheritableThreadLocal類對此提供支持,類名給出了用途的最好表示:可繼承的ThreadLocal!
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
同理,每個線程在創建的時候都會初始化InheritableThreadLocal
的引用,然後在線程執行初始化的生命周期階段時,對該變量進行初始化操作,如下圖:
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
線程的初始化周期中,首先判斷父線程的ThreadLocal是否為空,如果不為空則根據父線程的inheritableThreadLocals創建子線程的inheritableThreadLocals。
InheritableThreadLocal的實現也非常簡單:
- childValue: 計算子線程的初始化值,從這個方法中可以知道,子線程使用的是父線程中的value,並不是拷貝,如果需要使用副本拷貝,可以繼承重寫
- getMap: 返回當前線程持有的inheritableThreadLocals
- createMap: 如果當前線程的inheritableThreadLocals是空,則創建一個
以上三個api都是重寫ThreadLocal的。
Java中Integer和ThreadLocal