多线程编程的设计模式 不变模式(二)
不变模式(Immutable Pattern)顾名思义,它的状态在它的生命周期内是永恒的(晕,永恒的日月星晨,对象如人,
太渺小,谈不上永恒!),不会改变的.对于其中的不变类(Immutable Class),它的实例可以在运行期间保持状态永远不会被
改变,所以不需要采取共享互斥机制来保护,如果运用得当可以节省大量的时间成本.
请注意上面这段话,不变模式其中的不变类,说明不变类只是不变模式中一个组成部分,不变类和与之相辅的可变
类,以及它们之间的关系才共同构成不变模式!所以在涉及不变模式的时候一定要研究一个类是不变的还是可变的(Mutable).
在jdk中的String类和StringBuffer类就组成了一个不变模式.
还是先看具体的例子:
final class Dog{
private final String name;
private final int age;
public Dog(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){return this.name;}
public int getAge(){return this.age;}
public String toString(){
return "Dog's name = " + this.name + ",age = " + this.age;
}
}
1.Dog类本身被声明为final,可以保证它本身的状态不会被子类扩展方法所改变.
2.Dog类的所有成员变量都是final的,保证它在构造后不会被重新赋值.而且Dog类所有属性是private的,只提供getter访问.
3.Dog类的能传入的参数本身是Immutable的.这一点非常重要将在下面具体说明.
以上条件都是必要条件,而不是充要条件.
class DisplayDog extends Thread{
private Dog dog;
public DisplayDog(Dog dog){
this.dog = dog;
}
public void run(){
while(true){
System.out.println(this.getName() + " display: " + dog);
}
}
}
DisplayDog类是把一个Dog对象传入后,不断显示这个dog的属性.我们会同时用多个线程来显示同一dog对象,看看它们在共享
同一对象时对象的状态:
public class Test {
public static void main(String[] args) throws Exception {
Dog dog = new Dog("Sager",100);
new DisplayDog(dog).start();
new DisplayDog(dog).start();
new DisplayDog(dog).start();
}
}
运行这个例子你可以等上一个月,虽然运行一年都正常并不能说明第366天不出现异常,但我们可以把这样的结果认为是一种
说明.多个线程共享一个不变类的实例时,这个实例的状态不会发生改变.事实上它没有地方让你去改变.
在临界区模式中有些操作必须只允许有一个线程操作,而这个类本身以及对它的访问类中并不需要进行临界区保护,这就让多
个线程不必等待从而提高了性能.
既然有这么好的优势,那我们在需要临界区保护的对象为什么不都设计成不变类呢?
1.不变类设计起来有一定难度.对于上面这个用来示例的Dog,由于其本身的属性,方法都很简单,我们还可以充分地考虑到可
以改变它状态的各种情况.但对于复杂的类,要保证它的不变性,是一个非常吃力的工作.
不变类中,任何一个必要都件都不是充要条件,虽然连老骨灰都没有这么说过,但我还是要真诚地目光深邃语气凝重地告诉你.
没有任何条件是充要条件的意思就是如果任何一个必要条件你没考虑到,那它就会无法保证类的不可变性.没有规范,没有模
板,完全看一人设计人员的经验和水平.也许你自以为考虑很全面的一个"不变类"在其他高手面前轻而易举地就"可变"了.
2.不变类的种种必要条件限制了类设计的全面性,灵活性.这点不用多说,简单说因为是不变类,所以你不能A,因为是不变类,你
不能B.
当然,如果你是一人很有经验的设计者,你能成功地设计一个不变类,但因为它的限制而失去一些功能,你就要以使用与之相辅
的可变类.并且它们之间可以相互转换,在需要不变性操作的时候以不变类提供给用户,在需要可变性操作的时候以可变类提供
给用户.
在jdk中String被设计为不可变类,一旦生成一个String对象,它的所有属性就不会被变,任何方法要么返回这个对象本身的原
始状态,要么抛弃原来的字符串返回一个新字符串,而绝对不会返回被修改了的字符串对象.
但是很多时候返回新字符串抛弃原来的字符串对象这样的操作太浪费资源了.特别是在循环地操作的时候:
String s = "Axman";
for(int i=0;i<1000*1000;i++) s += "x";这样的操作是致命的.
那么这种时候需要将原始的不变的s包装成可变的StringBuffer来操作,性能的改变可能是成千上万倍:
StringBuffer sb = new StringBuffer(s); //将不变的String包装成可变的String;
for(int i=0;i<1000*1000;i++)
sb.append("x");
s = new String(sb); //将可变类封装成不变类.虽然可以调用toString(),但那不是可变到不变的转换.
在将可变类封装到不变类的时候要特别小心.因为你传入的引用在外面是可以被修改的.所以即使你不变类本身不能去改变属
性但属性有一个外部引用.可以在外面修改:
final class MutableDog{
private String name;
private int age;
public MutableDog(String name,int age){
this.name = name;
this.age = age;
}
public synchronized void setDog(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){return this.name;}
public int getAge(){return this.age;}
public synchronized String toString(){
return "Dog's name = " + this.name + ",age = " + this.age;
}
public synchronized ImmatableDog getImmatableDog(){
return new ImmatableDog(this);
}
}
final class ImmatableDog{
private final String name;
private final int age;
public ImmatableDog(String name,int age){
this.name = name;
this.age = age;
}
public ImmatableDog(MutableDog dog){
this.name = dog.getName();
this.age = dog.getAge();
}
public String getName(){return this.name;}
public int getAge(){return this.age;}
public String toString(){
return "Dog's name = " + this.name + ",age = " + this.age;
}
}
MutableDog类是可变的,可以满足我们利用对象的缓冲来让对象成为表示另一个实体的功能.但它们之间
随时可以根据需要相互转换,但是我们发现:
public ImmatableDog(MutableDog dog){
this.name = dog.getName();
this.age = dog.getAge();
}
这个方法是不安全的.当一个属性为"Sager",100的dog被传进来后,执行this.name = dog.getName();后,
如果线程切换到其它线程执行,那么dog的属性就可能是"p4",80,这时再执行this.age = dog.getAge();
我们就会得到一个属性为"Sager",80的这样一个错误的不可变对象.这是一个非常危险的陷井.在这里我们
可以通过同上来解决:
public ImmatableDog(MutableDog dog){
synchronized(dog){
this.name = dog.getName();
this.age = dog.getAge();
}
}
注意这里同步的MutableDog,它将会和MutableDog的setDog产生互斥.它们都需要获取同一MutableDog对象的
锁,如果MutableDog的setDog不是方法同步(synchronized(this)),即使ImmatableDog(MutableDog dog)中同步
了dog,也不能保证安全,它们需要在同一对象上互斥.
但同步也并不一定能保证传入的参数不可变:
我曾以下面这个例子来作为对一个Java程序员的终极测试,终极测试的意思是说,如果你不懂并不说明你水平
差,但如何你懂这个问题那就没有必要测试其它问题了.
public static void test(Object[] objs){
java.security.BasicPermission bp = xxxxx;
for(Object o: objs){
bp.checkGuard(o);
}
for(Object o: abjs){
o.xxx;
}
}
当一个数据被传入后我们需要对其中的每个元素做安全性检查,如果通不过bp.checkGuard(o);自己会抛出
异常的.但如果objs[0]被bp.checkGuard(o);过后,外面的线程通过objs去修改objs[0],这时就会把一个没有
经过安全检查的对象绕过bp.checkGuard(o);而直接被调用.假如Runtime.exec(String[] args)就是这样实
现我们可以想象会出现什么问题.
所以对于这样的传入参数,我们可以将其在方法类复制为本地变量(数组).或使用它的深度clone,打断与方法
外的联系:
public static void test(Object[] objs){
Object tmp = new Object[objs.lenth];
System.arrayCopy(objs,tmp,0,0,objs.lenth);
java.security.BasicPermission bp = xxxxx;
for(Object o: tmp){
bp.checkGuard(o);
}
for(Object o: tmp){
o.xxx;
}
}
先说到这里吧.休息一下.
分享到:
相关推荐
为能和大家能共同探讨"设计模式",我将自己在学习中的心得写下来,只是想帮助更多人更容易理解 GoF 的《设计模式》。由 于原著都是以C++为例, 以Java为例的设计模式基本又都以图形应用为例,而我们更关心Java在中间件等...
**C#设计模式第二版PPT**是一套深入解析C#编程中设计模式的教育资料,旨在帮助开发者提升代码质量,提高软件设计能力。设计模式是面向对象编程中的一种最佳实践,是解决常见问题的经验总结,是软件开发中的重要工具...
书中会展示如何实现和使用观察者模式,以及如何处理多线程环境下的事件传递。 4. **代理模式**:ContentProvider是Android中的数据访问代理,它提供了一种统一的数据访问接口。读者将了解如何通过代理模式实现数据...
《JAVA设计模式》是一本深入探讨Java编程中设计模式的宝贵资源。设计模式是软件开发中的通用解决方案,它们是经过时间验证的、在特定场景下解决问题的最佳实践。这本书的.chm格式表明它可能是一个帮助文件或电子书,...
缺点是在多线程环境下需要特别注意线程安全性。 - **1.4 Builder模式** - **定义**:将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。 - **应用场景**:当创建的对象较复杂,由多个...
#### 二、设计模式分类 设计模式可以分为三种主要类型:创建型模式、结构型模式和行为型模式。此外,还有针对特定领域(如并发编程)的设计模式。 1. **创建型模式**:关注的是如何创建对象。常见的创建型模式包括...
在Linux多线程编程中,理解和掌握这些概念和函数对于进行高效、稳定的程序设计至关重要。程序员在实际开发中,要根据具体需求选择合适的线程同步机制和进程间通信方法,以实现程序的高效运行。此外,编程时要考虑到...
例如,饿汉式通过静态初始化器在类加载时就创建实例,而懒汉式则在首次调用时才创建,双重检查锁定则在多线程环境下保证了线程安全。 其次,工厂模式是一种创建型设计模式,提供了一种创建对象的最佳方式,使得代码...
"C#设计模式"这一主题,特别是"设计模式的思想:对通用的对象间的相互作用方式进行记录和编目",主要探讨了如何在C#编程环境中应用这些通用模式。 设计模式的出现源于对良好软件设计原则的追求,包括可重用性、灵活...
在多线程环境中,单例模式可以保证线程安全。示例代码通常会包含一个私有构造函数和一个静态方法来获取单例实例。 2. **工厂模式**:定义创建对象的接口,让子类决定实例化哪一个类。它封装了创建对象的过程,使得...
《Head First设计模式》是一本深受开发者喜爱的设计模式教程,其高清中文版为中国的程序员们提供了深入理解设计模式的便利。这本书以独特的视觉和轻松易懂的方式解释了23种经典的GoF设计模式,旨在帮助读者提升软件...
在多线程环境下,单例模式需要考虑线程安全问题,C++中可以使用静态成员函数或std::call_once来实现。 2. **工厂模式**:提供一个创建对象的接口,但让子类决定实例化哪一个类。工厂模式是一种创建型设计模式,它将...
在Java编程中,设计模式被视为提升软件质量的重要工具,尤其对于大型项目,设计模式的应用使得团队协作更为高效。 一、单例模式(Singleton) 单例模式确保一个类只有一个实例,并提供一个全局访问点。这种模式在...
设计模式是软件开发中的宝贵经验总结,它们是解决常见问题的最佳实践,被广泛应用于各种编程语言,包括C#。在C#中应用设计模式可以提高代码的可读性、可维护性和可复用性,是成为一名优秀的C#开发者不可或缺的知识。...
【C++设计模式-单实例】是C++编程中一种重要的设计模式,它涉及到软件设计中的对象创建和管理。设计模式是一种在特定场景下解决常见问题的模板,它基于前人的经验总结,使得代码可读性更强,也更易于重用。在C++中,...
9. **并发设计模式**:书中涵盖了如生产者消费者模式、工作窃取模式等常见的并发设计模式,帮助开发者构建更高效的并发系统。 10. **线程性能调优**:最后,书中有章节专门讨论线程性能优化,包括如何确定合适的...
策略设计模式是一种行为设计模式,它使算法族可以彼此独立变化,同时让代码使用这些算法的方式保持不变。洛基库通过模板技术实现了这一模式,允许用户在运行时选择不同的策略或算法,提高了代码的灵活性和可扩展性。...
在C#编程语言中,设计模式的应用可以帮助我们编写出更灵活、可维护和可扩展的代码。本篇文章将深入探讨标题"设计模式Demo C#"所涵盖的一些关键设计模式,并通过代码示例来阐述其原理和应用场景。 首先,单例...
模板方法模式广泛应用于框架设计、游戏引擎、多线程编程等领域。例如,Java的JDBC API就使用了模板方法模式,提供了数据库操作的基本流程,而用户只需关注具体的SQL执行细节。 ### 实现示例 ```java abstract class...