多线程-不可变模式

在没有以不变模式来介绍String的时候,我们先来看String类的其它几个属性,其实String类要说的东西太多,这里只说几个必要的知识:
String s1 = “1111”;
String s2 = “1111”;
String 是唯一可以直接赋常量值的类(这只是对于程序员而言,也就从语法而言),对于这样的字面值赋值,其底层就是调用String s1 = new String(“1111”). .intern();
即先在字符串缓存池中查找是否有字符常量”1111”,如果有测将s1指向这个对象.没有则先创建对象放入字符串缓存池,然后将s1指向它,并销毁堆中的对象,当String s2 = “1111”;时仍然是调用
String s2 = new String(“1111”). .intern();这时已经找到字符常量”1111”,所以s2也指向了字符串缓存池中那个对象,并销毁了堆中的”1111”,这样做的结果就是直接赋字面值的语句如果字面值相同它们就都指向同一对象.
在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;
        }
    }

posted @   程序猿进化之路  阅读(165)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示