`
yesjavame
  • 浏览: 657299 次
  • 性别: Icon_minigender_2
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

多线程编程的设计模式 不变模式(二)

阅读更多


多线程编程的设计模式 不变模式(二)

不变模式(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;
}
}


先说到这里吧.休息一下.

分享到:
评论

相关推荐

    二十三种设计模式【PDF版】

    为能和大家能共同探讨"设计模式",我将自己在学习中的心得写下来,只是想帮助更多人更容易理解 GoF 的《设计模式》。由 于原著都是以C++为例, 以Java为例的设计模式基本又都以图形应用为例,而我们更关心Java在中间件等...

    Java并发编程实战

    4.1 设计线程安全的类 4.1.1 收集同步需求 4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的委托 4.3.1 示例:基于委托的车辆追踪器 ...

    Python核心编程第二版

     5.5.1 混合模式操作符   5.5.2 标准类型操作符   5.5.3 算术操作符   5.5.4 *位操作符(只适用于整型)   5.6 内建函数与工厂函数   5.6.1 标准类型函数   5.6.2 数字类型函数   5.6.3 仅...

    并发危险-解决多线程代码中的11个常见的问题

    多线程、.NETFramework 目录数据争用忘记同步粒度错误读写撕裂无锁定重新排序重新进入死锁锁保护戳记两步舞曲优先级反转实现安全性的模式不变性纯度隔离并发现象无处不在。服务器端程序长久以来都必须负责处理基本...

    Java 并发编程实战

    4.1 设计线程安全的类 4.1.1 收集同步需求 4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的委托 4.3.1 示例:基于委托的车辆追踪器 ...

    ruby.fundamental:Ruby示例和参考的基础编程。 它涵盖了线程,SOLID原理,设计模式,数据结构,算法。 阅读书籍。 网站https:github.comkhusnetdinovbetterdocs的仓库

    Ruby示例基础编程此回购保存了描述现代原理,模式的示例。网络文档:翻译:内容: RubyRuby的异常行为-几个小时的核心调试中我们看不到一个小细节。 ( 元编程元编程是指计算机程序的编写,这些计算机程序将其他程序...

    Python核心编程第二版(ok)

    Python核心编程第二版(ok) 第1部分 Python核心  第1章 欢迎来到Python世界   1.1 什么是Python   1.2 起源   1.3 特点   1.3.1 高级   1.3.2 面向对象   1.3.3 可升级   1.3.4 可扩展   ...

    Java并发编程(学习笔记).xmind

    (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 (2)建模简单:通过使用线程可以讲复杂并且异步的工作流进一步分解成一组简单并且同步的工作流,每个工作流在一个单独的线程...

    实战Java高并发程序设计(第2版)PPT模板.pptx

    3.1多线程的团队协作:同步控制 3.2线程复用:线程池 3.3不要重复发明轮子:jdk的并发容器 3.4使用jmh进行性能测试 3.2线程复用:线程池 3.3不要重复发明轮子:JDK的并发容器 3.4使用JMH进行性能测试 实战Java高并发...

    java8集合源码-usefullinks:有用的编程链接

    java8集合源码有用的链接 有用的编程链接 Java: 精选的 ...弹性设计模式:重试、回退、超时、断路器 系统设计入门 Spring Cloud Netflix 微服务——启动项目(文章系列) DDD、六边形、洋葱、清洁、C

    Java的六大问题你都懂了吗

    其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:所以,使用instanceof在绝大多数情况下并不是...

    javaSE代码实例

    16.1.1 多线程编程的意义 343 16.1.2 定义自己的线程 344 16.1.3 创建线程对象 345 16.1.4 启动线程 347 16.1.5 同时使用多个线程 348 16.2 线程的状态 350 16.3 线程的调度 351 16.3.1 睡眠 351 ...

    亮剑.NET深入体验与实战精要2

    13.4.4 设计模式、条件外置及反射技术的应用 471 13.5 面向对象分析(OOA)的方法 475 13.6 面向对象设计的原则 478 13.6.1 优先使用(对象)组合,而非(类)继承 478 13.6.2 针对接口编程,而非(接口的)实现 481...

    亮剑.NET深入体验与实战精要3

    13.4.4 设计模式、条件外置及反射技术的应用 471 13.5 面向对象分析(OOA)的方法 475 13.6 面向对象设计的原则 478 13.6.1 优先使用(对象)组合,而非(类)继承 478 13.6.2 针对接口编程,而非(接口的)实现 481...

Global site tag (gtag.js) - Google Analytics