首先让我简单解释一下所谓”发布”。
发布(publish),使对象可以在当前作用域之外的代码中可见,如果该对象被发布,则该对象的非私有域中引用的所有实例同样也会被发布。
不仅仅是作为一个field,当一个对象作为一个方法的参数或者在公有方法中作为返回引用,这都属于发布。
而相对地,对于错误的发布,我们将其称为逸出(escape)。
那么,什么是”错误的发布”? 比如发布导致封装性的破坏(可能直接导致无法安全地进行继承)、线程安全性问题(尤其是不变性条件的破坏)。
仅仅是修改了访问修饰,但可能导致难以预测的问题,并发编程时发布也变得尤为敏感。
那如何能避免逸出? 最简单的方法就是不发布。
线程封闭 ->
但总不能一直这样下去,资源的共享也是线程并发的一大优势,于是如何进行安全的发布显得非常重要。
那么不可变对象的发布是否也属于发布? 当然,这也是安全发布的一种策略。
(保证不可变 ->)
任何线程都可以在没有进行额外同步处理的情况下安全访问不可变对象。
但是不可变并不仅仅是final关键字那么简单,如果指向的对象是可变的则仍需要进行同步处理。
看一段代码,如果只是单线程应用,,则几乎没有问题(其实问题还是有的),但是从并发的角度看,发布出来的holder对象甚至没有考虑可见性问题,而且对象尚未创建完成就已经发布,其他线程看到这个holder时将是不一致状态的holder:
publicclassStuffIntoPublic{publicHolderholder;publicvoidinitialize(){holder=newHolder(42);}}
于是,为了应对状态不一致的情况,我们将Holder设计为…谁会想用这样的对象…
publicclassHolder{privateintn;publicHolder(intn){this.n=n;}publicvoidassertSanity(){if(n!=n)thrownewAssertionError(“Thisstatementisfalse.”);}}
既然如此,那如何安全并友好地对可变对象进行同步? 以下是几点建议:
以下面的代码为例:
publicclassMonitorVehicleTracker{privatefinalMap<String,MutablePoint>locations;publicMonitorVehicleTracker(Map<String,MutablePoint>locations){this.locations=deepCopy(locations);}publicsynchronizedMap<String,MutablePoint>getLocations(){returndeepCopy(locations);}publicsynchronizedMutablePointgetLocation(Stringid){MutablePointloc=locations.get(id);returnloc==null?null:newMutablePoint(loc);}publicsynchronizedvoidsetLocation(Stringid,intx,inty){MutablePointloc=locations.get(id);if(loc==null)thrownewIllegalArgumentException(“NosuchID:”+id);loc.x=x;loc.y=y;}privatestaticMap<String,MutablePoint>deepCopy(Map<String,MutablePoint>m){Map<String,MutablePoint>result=newHashMap<String,MutablePoint>();for(Stringid:m.keySet())result.put(id,newMutablePoint(m.get(id)));returnCollections.unmodifiableMap(result);}}
整个对象中只有一个locations,我们使用final修饰保证了其安全创建。
但只是这一点还不够,构造器中我们并没有直接将参数引用到location上,而是进行一次静态的deep copy,并使用Collections.unmodifiableMap将结果装饰一遍。
接着,我们在getter/setter中做了同步处理,这一点正是OO特性对并发良好支持的体现。
getLocation中我们获得location后并没有直接将其返回,而是重新创建一个新对象,以此防止逸出。
功夫不负有心人。