1. 程式人生 > >String常量池和synchronized關鍵字

String常量池和synchronized關鍵字

在java中String是一個比較特殊的類,String有一個自己的常量池,存放了初始化的字串,這一個特性經常能導致一些坑,下面簡單的介紹一點。

一,用常量賦值的字串

String a=”abc”;

當執行這條語句時,先在常量池中查詢是否有”abc”物件,如果有則建立物件a並指向這個物件,如果沒有則先在常量池中建立”abc”物件,然後建立物件a並指向這個物件。

最終a是指向常量池的。

所以:

String a="abc";

String b="abc";

System.out.println(a==b);

輸出的是true

二,new的字串

String a=new String("abc");

先在常量池中查詢是否有”abc”物件,如果有則把常量池中的物件複製一份,然後建立物件a並指向複製出來的這個物件,如果沒有則先在常量池中建立”abc”物件,然後複製一份,然後建立物件a並指向複製出來的這個物件。

最終a不是指向常量池的。而且在第一次使用該字串時,記憶體裡存了兩個這個字串的物件,一個在堆裡一個在常量池裡。

所以:

String a=new String("abc");

String b="abc";

System.out.println(a==b);

輸出的是false

再一個例子:

String a=new String("abc");

String b= new String("abc");

System.out.println(a==b);

輸出的也是false

三,String的intern()方法

在這裡要特別提到String的intern()方法,根據jdk對這個方法的描述,翻譯一下大概是這樣的:

當這個方法被呼叫時,如果常量池中有同值的字串,則返回常量池中的字串。如果常量池中沒有這個字串,則在常量池中建立這個字串並返回。所謂同值,指的是equals()方法返回值為true。

所以,以下程式碼輸出的是true

String a="abc";

String b=new String("abc").intern();

System.out.println(a==b);

四,System的identityHashCode()方法

另外提一下System.identityHashCode()方法,這個方法可以返回物件所屬類的原有的HashCode,這個方法適合應用於hashCode()方法被重寫的類。

比如本文中提到的String類的hashCode方法就被重寫過,相同字串的hashCode往往是一樣的,而這個方法返回重寫前的那個hashCode()方法的返回值,比如:

String a="abc";

String b="abc";

String c=new String("abc");

String d=new String("abc");

System.out.println(System.identityHashCode(a));

System.out.println(System.identityHashCode(b));

System.out.println(System.identityHashCode(c));

System.out.println(System.identityHashCode(d));

輸出的內容是:

1502913001

1502913001

756151793

1982445652

可以看到,a和b確實是指向一個物件,而c和d都是堆中單獨的物件。

五,synchronized關鍵字和String

java的synchronized關鍵字在生效時就和記憶體地址有關,特別是當我們使用類似

synchronized(某個變數){}

這種格式時,引數物件的記憶體地址只要相同,synchronized關鍵字的同步功能就會生效,所以我們可以寫以下例子:

package test;

public class Test {

   public static void main(String[] args) {

      for (int i = 0; i < 10; i++) {    //新建10個執行緒一起搶鎖

         String key = new String("abc");

         TestThread t = new TestThread(key);

         t.start();

      }

   }

   /**

    * 使用構造時傳入的引數String作為鎖,觀察執行緒獲得鎖的情況

    */

   public static class TestThread extends Thread {

      public String key;

      public TestThread(String key) {

         this.key = key;

      }

      @Override

      public void run() {

         synchronized (key) {

            System.out.println(Thread.currentThread().getName() + " begin " + key);

            try {

                Thread.sleep(3000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }


            System.out.println(Thread.currentThread().getName() + " end " + key);

         }

      }

   }

}

這個例子中的同步語句是失效的,每個執行緒都拿到了鎖並且並行執行了,因為每次定義一個執行緒時使用的String物件是用new的方式獲得的,在記憶體中是不同的地址。

然後改一下key的定義方式,改成以下的例子:

package test;

public class Test {

   public static void main(String[] args) {

      for (int i = 0; i < 10; i++) {    //新建10個執行緒一起搶鎖

         String key = "abc";

         TestThread t = new TestThread(key);

         t.start();

      }

   }

   /**

    * 使用構造時傳入的引數String作為鎖,觀察執行緒獲得鎖的情況

    */

   public static class TestThread extends Thread {

      public String key;

      public TestThread(String key) {

         this.key = key;

      }

      @Override

      public void run() {

         synchronized (key) {

            System.out.println(Thread.currentThread().getName() + " begin " + key);

            try {

                Thread.sleep(3000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }


            System.out.println(Thread.currentThread().getName() + " end " + key);

         }

      }

   }

}

這個例子中的同步語句是生效的,每個執行緒爭搶同一把鎖,雖然key是在每次迴圈中定義的,但是其實使用的都是常量池中的物件,synchronized認為這都是一個物件,所以同步生效。

以上。