【Java必修課】String.intern()原來還能這麼用(原理與應用)

1 簡介

String.intern()是JDK一早就提供的native方法,不禁Java實現,而是底層JVM實現,這讓咱們對它的窺探提升了難度。特別是在Oracle收購了Sun公司後,源代碼不開源了,更沒法深刻研究了。但咱們仍是有必要儘可能地去探索。java

本文將主要講解一下String.intern()方法的原理、特色,並介紹一個新奇的應用。app

2 String的池化

方法intern()的做用就是將String池化,這個池是String的常量池。不一樣版本的JDK有不一樣的實現。ui

2.1 不一樣實現與不一樣內存空間

  • JDK 6:intern()方法會把首先遇到的字符串複製一份到永久代中,而後返回永久中的實例引用;若是不是首次,說明常量池中已經有該字符串,直接返回池中的引用。常量池在永久代(PermGen)中。
  • JDK 7:intern()方法首次遇到字符串時,不會複製實例,而是直接把該字符串的引用記錄在常量池中,並返回該引用;若是不是首次,則直接返回池中引用。JDK 7常量池在堆中。
  • JDK 8:功能與JDK 7相似。常量池在元空間Metaspace中,元空間不在虛擬機內存中,而是使用本地內存。

2.2 常量池大小差別

這個所謂的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。對象

3 例子分析

經過例子,來理解一下就更清晰了。JDK 7和8應該表現一致,本文使用JDK 8。內存

3.1 JDK 8

先演示JDK 8的狀況:ci

例子1

String str1 = new String("pkslow");
System.out.println(str1.intern() == str1);

結果:false

分析:由於使用了字面量,在編譯期就會把字符串放到常量池,當使用new String()時,會建立新的對象。因此常量池中的引用與建立的對象引用不一樣。

例子2

String str1 ="pkslow";
System.out.println(str1.intern() == str1);

結果:true

分析:與上個例子對比,將常量池的地址賦值給了str1變量,因此相等。

例子3

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不相等。

例子4

String str = new StringBuilder("ja").append("va").toString();
System.out.println(str.intern() == str);

結果:false

分析:按理說與上個例子的(1)同樣,應該爲true纔對。問題在於java它是一個比較特殊的字符串,已經在常量池中存在了,因此不相等。至於爲什麼會存在,個人猜測是有兩種可能:其它JDK的Java代碼有該常量;JVM代碼直接把某些特殊字符串放到了常量池。這個沒有深究了。

3.2 JDK 6的不一樣

當咱們知道了原理後,不一樣表現就能夠很容易判斷出來了。以下例子:

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。

知道了基本原理,更多狀況就能夠具體分析了,再也不一一贅述。

4 一種少見的應用

以前已經說過,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

能夠看出,同一時間同一個城市不會同時得到資源,而不一樣城市能夠同時得到資源來處理。這種案例其實有其它更優雅的方案,這不是本文的重點,就不贅述了。

5 總結

本文介紹了String.intern()方法的原理和不一樣JDK版本的表現,並經過多個例子與一個應用加深理解。但願對你們理解String和JVM有幫助。


歡迎關注公衆號<南瓜慢說>,將持續爲你更新...

file

多讀書,多分享;多寫做,多整理。

相關文章
相關標籤/搜索