String.intern()
是JDK一早就提供的native方法,不禁Java實現,而是底層JVM實現,這讓咱們對它的窺探提升了難度。特別是在Oracle收購了Sun公司後,源代碼不開源了,更沒法深刻研究了。但咱們仍是有必要儘可能地去探索。java
本文將主要講解一下String.intern()
方法的原理、特色,並介紹一個新奇的應用。app
方法intern()
的做用就是將String池化,這個池是String的常量池。不一樣版本的JDK有不一樣的實現。ui
intern()
方法會把首先遇到的字符串複製一份到永久代中,而後返回永久中的實例引用;若是不是首次,說明常量池中已經有該字符串,直接返回池中的引用。常量池在永久代(PermGen)中。intern()
方法首次遇到字符串時,不會複製實例,而是直接把該字符串的引用記錄在常量池中,並返回該引用;若是不是首次,則直接返回池中引用。JDK 7常量池在堆中。這個所謂的String常量池,其實就是一張哈希表,跟HashMap
相似,因此也是有大小限制和哈希衝突可能。常量池越大,哈希衝突可能性越小。this
JDK 6早期版本,池大小爲常量1009,後期變得可配置,經過參數-XX:StringTableSize=N
指定。大小也會受限於永久代的大小,建議避免使用intern()
方法,防止形成PermGen內存溢出。spa
JDK 7將常量池移到堆後,能夠存放更多常量,也同樣經過參數可配置大小。在Java 7u40後,常量池默認大小增長到了60013。線程
JDK 8默認大小一開始就是60013,依舊支持參數配置。code
總的來講,-XX:StringTableSize的默認值在Java 7u40之前爲1009,Java 7u40之後改成60013。對象
經過例子,來理解一下就更清晰了。JDK 7和8應該表現一致,本文使用JDK 8。內存
先演示JDK 8的狀況:ci
String str1 = new String("pkslow"); System.out.println(str1.intern() == str1);
結果:false
分析:由於使用了字面量,在編譯期就會把字符串放到常量池,當使用new String()
時,會建立新的對象。因此常量池中的引用與建立的對象引用不一樣。
String str1 ="pkslow"; System.out.println(str1.intern() == str1);
結果:true
分析:與上個例子對比,將常量池的地址賦值給了str1變量,因此相等。
String str1 = new StringBuilder("pk").append("slow").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("pk").append("slow").toString(); System.out.println(str2.intern() == str2);
結果:true false
分析:
(1)第一句建立了一個新的字符串對象,str1爲其引用,調用str1.intern()
時會把它的引用放到常量池中並返回,因此是同一個引用。
(2)在(1)中已經放在常量池了,因此str2.intern()
返回的是str1,與str2不相等。
String str = new StringBuilder("ja").append("va").toString(); System.out.println(str.intern() == str);
結果:false
分析:按理說與上個例子的(1)同樣,應該爲true纔對。問題在於java它是一個比較特殊的字符串,已經在常量池中存在了,因此不相等。至於爲什麼會存在,個人猜測是有兩種可能:其它JDK的Java代碼有該常量;JVM代碼直接把某些特殊字符串放到了常量池。這個沒有深究了。
當咱們知道了原理後,不一樣表現就能夠很容易判斷出來了。以下例子:
String str1 = new StringBuilder("pk").append("slow").toString(); System.out.println(str1.intern() == str1);
JDK 6結果:false
JDK 8結果:true
由於JDK 6對於首次遇到的字符串,會複製一份到常量池並返回其引用,與str1的引用不是同一個,因此爲false。而JDK 8只是將str1的引用在常量池記錄而後返回,仍是同一個,因此爲true。
知道了基本原理,更多狀況就能夠具體分析了,再也不一一贅述。
以前已經說過,String.intern()
方法本質就是維持了一個String的常量池,並且池裏的String應該都是惟一的。這樣,咱們即可以利用這種惟一性,來作一些文章了。咱們能夠利用池裏String的對象來作鎖,實現對資源的控制。好比一個城市的某種資源同一時間只能一個線程訪問,那就能夠把城市名的String對象做爲鎖,放到常量池中去,同一時間只能一個線程得到。
具體代碼以下:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class StringInternMultiThread { private String city; public StringInternMultiThread(String city) { this.city = city; } public void handle() { synchronized (this.city.intern()) { System.out.println(city + ":Fetched the lock"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(city + ":Release the lock"); } } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(6); StringInternMultiThread guangzhou = new StringInternMultiThread("Guangzhou"); StringInternMultiThread shenzhen = new StringInternMultiThread("Shenzhen"); StringInternMultiThread beijing = new StringInternMultiThread("Beijing"); executorService.execute(guangzhou::handle); executorService.execute(guangzhou::handle); executorService.execute(guangzhou::handle); executorService.execute(shenzhen::handle); executorService.execute(shenzhen::handle); executorService.execute(beijing::handle); executorService.shutdown(); } }
運行結果以下:
Guangzhou:Fetched the lock Shenzhen:Fetched the lock Beijing:Fetched the lock Beijing:Release the lock Shenzhen:Release the lock Guangzhou:Release the lock Shenzhen:Fetched the lock Guangzhou:Fetched the lock Shenzhen:Release the lock Guangzhou:Release the lock Guangzhou:Fetched the lock Guangzhou:Release the lock
能夠看出,同一時間同一個城市不會同時得到資源,而不一樣城市能夠同時得到資源來處理。這種案例其實有其它更優雅的方案,這不是本文的重點,就不贅述了。
本文介紹了String.intern()
方法的原理和不一樣JDK版本的表現,並經過多個例子與一個應用加深理解。但願對你們理解String和JVM有幫助。
歡迎關注公衆號<南瓜慢說>,將持續爲你更新...
多讀書,多分享;多寫做,多整理。