多线程设计模式之Immutable模式
Immutable
就是不可变,不会发生改变的意思,这意味着一个类创建对象之后,该对象就无法被修改了。任何对该对象所做的修改,都将创建一个新的不可变对象。就例如Java.lang.String,一旦创建一个String对象,那么我们就不可以对它状态进行修改了,如果对它进行修改,那么就会创建新的String对象。
class StringTest{
public static void main(String[] args) {
String s1="123"; //创建一个字符串对象 值为123
String s2=s1; //我们将s1,s2对象引用同时指向"123"
s1+="345"; //我们对s1进行修改
System.out.println(s1.equals(s2));//结果为false 说明s1和s2引用的不是同一个对象了
}
}
Immutable模式确保类的实例状态不会发生改变,那么该实例无论被多少个线程访问都不会发生改变,不需要执行线程的互斥处理,虽然 Immutable模式的缺点很多,但是我们巧妙利用,还是能在多线程中发挥很好的作用。
public final class ImmutableTest {
private final Date date;//date虽然使用final指定,但是date是可变类 我们可以通过setTime()改变date的状态
private final String str; //String本身就是不可变类 所以不需要对其进行考虑
public ImmutableTest(Date date,String str) {
long time=date.getTime();
if(time<System.currentTimeMillis()){
throw new IllegalArgumentException("Can not set it"+
"for past time: "+date);
}
this.date=new Date(time); //新建一个Date类保证内部的date对象不会泄露出去改变其状态
this.str=str;
}
public Date getDate() {
return (Date) date.clone(); //为了保持不变性 我们这里可以返回data的副本 那么外界的修改对data没有影响
}
public String getStr() {
return str;
}
public static void main(String[] args) throws InterruptedException {
Date date=new Date();
ImmutableTest test=new ImmutableTest(date,"test");
date=test.getDate();
Thread.sleep(1000);
date.setTime(System.currentTimeMillis());
System.out.println(test.getDate().getTime());
System.out.println(date.getTime());
}
}
如何编写Immutable类呢? 需要注意一下几点。
- 构造对象后不可修改对象内部的状态,即使可以修改,任何修改的结果都是产生新的不可变对象,例如String,修改后是新的对象,对原来对象的状态不影响。
- 必须保证类内部的字段是私有的,且用final关键字修饰的。
- 在对象构造的过程,必须保证不泄露任何对象。
- 类应该用final修饰,避免子类继承父类会改变父类的不变性(强不变性),或者所有的方法都加上final(弱不变性).
- getter()方法必须提供的是不可变的对象或者对象的副本。
Immutable类的优点
- 不可变对象默认是线程安全的,可以在并发环境下不需要进行同步即可共享。
- 简化了开发的难度,可以在多个线程中共享而不需要做额外的同步处理。
- 减少了同步的处理,大大提高了开发效率。
- 不可变对象的一大好处就是可重用性,我们可以缓存不可变对象并且重用他们。
- 例如: 不变性保证了hashCode 的唯一性,因此可以放心地进行缓存而不必每次重新计算新的哈希码。而哈希码被频繁地使用, 比如在hashMap 等容器中。将hashCode 缓存可以提高以不变类实例为key的容器的性能。
Immutable类的缺点
- 由于类的对象一创建就不可变了,若我们想修改它的状态,那么就必须新建一个不可变类,花费的代价是巨大的,不可避免地会产生大量的垃圾。
- 字符串是一个主要的例子,它可以创建大量的垃圾,并且可能因为大量的垃圾回收而可能减慢应用程序。