Immutable
Immutable
是什麼意思?不變的、不發生改變的意思。在JDK
中有很多的類被設計成不可變的,舉個大家經常用到的類java.lang.String
,String
類被設計成不可變。String
所表示的字符串的內容絕對不會發生變化。因此,在多線程的情況下,String
類無需進行互斥處理,不用給方法進行synchronized
或者lock
等操作,進行上鎖、爭搶鎖、解鎖等流程也是有一定性能損耗的。因此,若能合理的利用Immutable
,一定對性能的提升有很大幫助。
爲什麼String不可變?
那麼爲什麼String
是不可變的呢?滿足那些條件纔可以變成不可變的類?大家可以打開String
類的源碼:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
/**
* Allocates a new {@code String} so that it represents the sequence of
* characters currently contained in the character array argument. The
* contents of the character array are copied; subsequent modification of
* the character array does not affect the newly created string.
*
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**
* Converts this string to a new character array.
*
* @return a newly allocated character array whose length is the length
* of this string and whose contents are initialized to contain
* the character sequence represented by this string.
*/
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
}
通過上邊的這段代碼你會發現:
1、首先
String
類本身是被final
修飾過的,表明該類無法進行擴展,無法創建子類。因此類中聲明的方法則不會被重寫。
2、String
類中的成員變量都被final
修飾,同時均爲private
,被final
修飾則表示成員變量
不會被setter
方法再次賦值,private
則表示成員變量均爲類私有,外部無法直接調用。
3、String
類中的成員變量都沒有setter
方法,避免其他接口調用改變成員變量的值。
4、通過構造器初始化所有成員,同時在String
中賦值是用的Arrays.copyOf
等深拷貝方法。
5、在getter
方法中,不要直接返回對象本身,而是克隆對象,並返回對象的拷貝。
以上5點保證String
類的不可變性(immutability
)。
示例程序
下面咱們通過一些簡單的示例程序來實驗Immutable
,自己動手,豐衣足食,多動手會有好處的。下邊定義三個類。
類名 | 說明 |
---|---|
People |
表示一個人的類 |
PeopleThread |
表示People 實例的線程的類 |
Main |
測試程序行爲的類 |
下邊看下每個類的示例代碼,People
類,類以及成員變量均被final
修飾,同時只能通過構造函數來對成員變量賦值,沒有setter
方法:
public final class People {
private final String sex;
private final int age;
private final String address;
public People(String sex, int age, String address) {
this.sex = sex;
this.age = age;
this.address = address;
}
public String getSex() {
return sex;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "People{" +
"sex='" + sex + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
PeopleThread
類:
public class PeopleThread extends Thread{
private People people;
public PeopleThread(People people){
this.people = people;
}
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName() + " prints " + people);
}
}
}
Main
類:
public class Main {
public static void main(String[] args) {
People people = new People("男",27,"北京");
new PeopleThread(people).start();
new PeopleThread(people).start();
new PeopleThread(people).start();
}
}
下邊是執行結果:
Thread-0 prints People{sex='男', age=27, address='北京'}
Thread-0 prints People{sex='男', age=27, address='北京'}
Thread-0 prints People{sex='男', age=27, address='北京'}
Thread-0 prints People{sex='男', age=27, address='北京'}
Thread-2 prints People{sex='男', age=27, address='北京'}
Thread-2 prints People{sex='男', age=27, address='北京'}
Thread-2 prints People{sex='男', age=27, address='北京'}
Thread-2 prints People{sex='男', age=27, address='北京'}
Thread-1 prints People{sex='男', age=27, address='北京'}
Thread-1 prints People{sex='男', age=27, address='北京'}
Thread-1 prints People{sex='男', age=27, address='北京'}
Thread-1 prints People{sex='男', age=27, address='北京'}
通過結果就可以發現,無論啓動多少個線程,打印的結果其實都是一樣的。因爲People
類本身在實例被創建且字段初始化之後,字段的值就不會再被修改,實例的狀態在初始化之後就不會再發生改變,因此也不需要在進行加鎖、解鎖等操作。因爲想破壞也破壞不了。但是有一點比較難的就是如果確保Immutability
,因爲在對類的創建過程中少個final
,多個setter
等,那麼就無法保證類的Immutability
。
何時使用呢?
上邊簡單的講解了下Immutable
模式,那麼在那些情況下考慮使用Immutability
不可變性呢?
實例創建後,狀態不再發生變化時
這個可以參考上邊的示例,一個人的屬性被賦值之後就不會發生改變。這種情況下就可以考慮不可變性來實現。
實例是共享的,且被頻繁訪問時
Immutable
模式的優點是“不需要使用synchronized來保護”,這就意味着能夠在不是去安全性和生存性的前提下提高性能。當實例被多個線程共享,且有可能被頻繁訪問時,Immutable
模式的優點就會凸顯出來。關於不適用synchronized
能提高多少性能?下邊做個實現:
public class NoSynchronized {
private static final long CALL_COUNT = 1000000000L;
public static void main(String[] args) {
trial("NotSynch",CALL_COUNT,new NotSynch());
trial("Synch",CALL_COUNT,new Synch());
}
private static void trial(String msg,long count,Object object){
System.out.println(msg + ": BEGIN");
long start_time = System.currentTimeMillis();
for (long i = 0; i< count; i++){
object.toString();
}
System.out.println(msg + ": END");
System.out.println("Elapsed time = " + (System.currentTimeMillis() - start_time) + "msec");
}
static class NotSynch{
private final String name = "NotSynch";
@Override
public String toString() {
return "NotSynch{" +
"name='" + name + '\'' +
'}';
}
}
static class Synch{
private final String name = "Synch";
@Override
public synchronized String toString() {
return "Synch{" +
"name='" + name + '\'' +
'}';
}
}
}
查看執行結果,差了44倍,這是在完全沒有發生線程衝突的情況下測試的,所以測試的時間就是獲取和釋放實例鎖所花費的時間。當然也跟本地的環境也會對時間差有一定的影響,因此僅供參考:
NotSynch: BEGIN
NotSynch: END
Elapsed time = 693msec
Synch: BEGIN
Synch: END
Elapsed time = 31162msec
哪些情況會破壞不可變性?
getter
返回的不是不可變的類
舉個例子,比如變量爲StringBuffer
類型:
public final class UserDetail {
private final StringBuffer detail;
public UserDetail(String sex,int age,String address){
this.detail = new StringBuffer(sex + ":" + age + ":" + address);
}
public StringBuffer getDetail() {
return detail;
}
@Override
public String toString() {
return "UserDetail{" +
"detail=" + detail +
'}';
}
public static void main(String[] args) {
UserDetail userDetail = new UserDetail("男",27,"北京");
//顯示
System.out.println(userDetail);
StringBuffer detail = userDetail.getDetail();
detail.append(":::").append("test");
//再次顯示
System.out.println(userDetail);
}
}
運行結果:
UserDetail{detail=男:27:北京}
UserDetail{detail=男:27:北京:::test}
在get
結果之後重新進行append
操作,StringBuffer
包含修改內部狀態的方法,所以detail
字段的內容也是可以被外部修改的。
- 在一個類中使用了其他的類,其他的類是可變的
當一個不可變的類中使用了其他的可變類之後,那麼受影響不可變的類也會變成可變的類。
擴展
在Java
的標準類庫中,有些類也用到了Immutable
模式
java.lang.String
java.math.BigInteger
&&java.math.BigDecimal
java.util.regex.Pattern
java.lang.Integer
&&java.lang.Short
等基本數據類型包裝類
在一開始的時候說到了String
是不可變的,那麼是否真的不可變呢?有一種方式是可以更改其狀態的,反射機制。
//創建字符串"Hello World", 並賦給引用s
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//獲取String類中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改變value屬性的訪問權限
valueFieldOfString.setAccessible(true);
//獲取s對象上的value屬性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改變value所引用的數組中的第5個字符
value[5] = '#';
System.out.println("s = " + s); //Hello#World
運行結果
s = Hello World
s = Hello#World
發現String的值已經發生了改變。也就是說,通過反射是可以修改所謂的“不可變”對象的。這些在使用的時候都是需要注意的地方。