在并发软件的开发过程中,同步操作是不可避免的。当多线程对同一个对象进行读写操作时,为了保证对象数据的一致性和正确性,有必要对对象进行同步。而同步对性能是有相当的损耗的。为了尽可能的除去这些同步操作,提高程序的并行性能,可以使用一种不可改变的对象,依靠对象的不变性,可以确保在没有同步的多线程环境中依然始终保持内部状态的一致性和正确性。这就是不变模式。
不变模式天生就是对多线程友好的,它的核心思想是:一个对象一旦被创建,则它的内部状态将永远不会发生改变。所以,没有一个线程可以修改其内部状态和数据,同时其内部的状态也不会自行发生改变。基于这些特点,对不变模式的多线程操作不需要进行同步控制。
注意,不变模式和只读属性是有一定的区别的。不变模式比只读属性有更强的一致性和不变性。对只读属性而言,对象本身不能被其他线程修改,但对象的自身状态却可以自行修改。比如:一个对象的存活时间是只读的,因为任何一个第三方线程都不能修改这个属性,但这是一个可变的属性,随着时间的推移,存活时间时刻发生在变化。而不变模式则要求,无论出于什么原因,对象创建后,其内部状态和数据都要保持绝对的稳定。
不变模式有两种形式:弱不变模式和强不变模式。它们的区别是子类是否是不变的,强不变类都是final的。
制定不变对象注意点:
❤ 去除setter方法以及所有修改自身属性的方法;
❤ 将所有的属性设置为私有,并用final标记,确保其不可修改;
❤ 确保没有子类可以重载修改它的行为;
❤ 有一个可以完整创建对象的构造函数;
下面举例说明:
1 public final class Product {//final修饰类,确保无子类 2 private final String no; //私有属性,不会被其他对象获取,final保证属性不会被第二次赋值 3 private final String name; 4 private final double price; 5 6 //创建对象时,必须指定数据,因为创建后,无法进行修改 7 public Product(String no,String name,double price){ 8 super(); 9 this.no = no; 10 this.name = name; 11 this.price = price; 12 } 13 14 public String getNo() { 15 return no; 16 } 17 18 public String getName() { 19 return name; 20 } 21 22 public double getPrice() { 23 return price; 24 } 25 }
上述例子实现了一个不变的产品对象,它拥有序列号、名称、价格三个属性。
从上述例子中可以看出,final在不变模式中起到了重要的作用。对属性的final定义确保所有数据只能在对象被创建时赋值1次,之后,就永远不会发生改变。而class的final修饰确保了此类不会被继承。根据里式替换原则,子类可以完全的替代父类。如果父类是不变的,那么子类也必须是不变的,但是实际上我们并不能约束这点,为了防止子类做出意外的行为,就直接禁止了继承。
JDK中的不变模式:
(1)String是一个强不变类。例如:String a = "hello",String b = "hello",Java虚拟机只会创建一盒字符串的实例,而这两个String对象都在共享这一个值,所以a == b 是true。如果字符串内容频繁变化,就不应该使用String类,应该使用StringBuffer、StringBuilder类。
(2)封装类,即8个原始类型的封装类(Integer、Float、 Double、Byte、Long、Short、Boolean、Character)都是强不变类。
不变模式的优缺点
(1)因为不能修改一个不变对象的状态,所以可以避免由此引起的不必要的程序错误;换言之,一个不变对象要比可变对象更加容易维护。
(2)因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样就可以省掉处理同步化的开销。一个不变对象可以自由地被不同的客户端共享。
(3)不需要保护性拷贝(Deffensive Copy)。
(4)不需要拷贝构造函数和clone。
(5)只计算一次hashcode。mutable object必须每次计算。
(6)不变模式的唯一的缺点是,一旦需要修改一个不变对象的状态,就只好创建一个新的同类对象。在需要频繁修改不变对象的环境里,会有大量的不变对象作为中间结果被创建出来,再被Java语言的垃圾收集器收集走,这是一种资源上的浪费。
在设计任何一个类的时候,应当慎重考虑其状态是否有需要变化的可能性。除非其状态有变化的必要,不然应当将它设计成不变类。
不变模式通过回避问题而不是解决问题的态度来处理多线程并发访问控制。不变对象是不需要进行同步操作的。由于并发同步会对性能产生不好的影响,因此,在需求允许的情况下,不变模式可以提高系统的并发性能和并发量。
参考:《Java高并发程序设计》 葛一鸣 郭超 编著: