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認為這都是一個物件,所以同步生效。
以上。