在并行软件开发过程中,同步操作似乎是必不可少的。
当多线程对同一个对象进行读写操作时,为了保证对象数据的一致性
和正确性,有必要对对象进行同步,但是同步操作对系统性能有损耗。
为了尽可能地去除这些同步操作,提高并行程序性能可用使用一种
不可改变的对象,依赖对象的不可变性,可用确保其在没有同步操作的
多线程环境中依然保持内部状态的一致性和正确性。这就是不变模式。
不变模式天生就是多线程友好的,它的核心思想是,一个对象一旦被创建,
它的内部状态将永远不会发生改变。没有一个线程可用修改其内部状态
和数据。同时其内部状态也绝不会自行发生改变。基于这些特性,
对不变对象的多线程操作不需要进行同步控制。
?同时还需要注意,不变模式和只读属性是有一定的区别的,不变模式比
只读属性具有更强的一致性和不变性。对只读属性的对象而言,
对象本身不能被其他线程修改,但是对象自身的状态却可能自行修改。
比如,一个对象的存活时间(对象创建时间和当前时间的时间差)是只读的,
任何一个第三方线程都不能修改这个属性,但是这是一可变的属性,
因为随着时间的推移,存活时间时刻都在发生变化。而不变模式则要求,
无论出于什么原因,对象自创建后,其内部状态和数据保持绝对的稳定。
因此,不变模式的主要使用场景需要满足以下两个条件。
1.当对象创建后,其内部状态和数据不再发生任何变化。
2.对象需要被共享,被多线程频繁访问。
在java语言中,不变模式的实现很简单。
为确保对象被创建后,不发生任何改变,并保证
不变模式正常工作,只需要注意以下四点即可。
1.去除setter方法及所有修改自身属性的方法。
2.将所有属性设置为私有,并用final标记,确保其不可修改。
3.确保没有子类可用重载修改它的行为。
4.有一个可用创建完整对象的构造函数。
package org.example;
import jdk.nashorn.internal.runtime.arrays.IntOrLongElements;
public final class Product {
private final String no;
private final String name;
private final double price;
public Product(String no,String name,double price){
super();
this.no = no;
this.name = name;
this.price = price;
}
public String getNo(){
return no;
}
public String getName(){
return name;
}
public double getPrice(){
return price;
}
}
在不变模式的实现中,final关键字起到了重要的作用。
对属性的final定义确保所有数据只能在对象被构造时
赋值1次。之后就永远不发生改变。而对class的final
确保了类不会有子类。根据里氏代换原则,
子类可用完全替代父类。如果父类是不变的。
那么子类也必须是不变的。
但实际上我们无法约束这点。
为了防止子类做出一点意外的行为,这里干脆把子类都禁用。
JDK中,不变模式的应用非常广泛,
其中最为典型的就是java.lang.String类。
所有的元数据类、包装类都是使用不变模式实现的。
主要的不变模式类型如下:
java.lang.String
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.Double
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Short
使用不变模式后,所有实例的方法
均不需要进行同步操作,保证了他们在多线程环境下的性能。