并发编程原则与技术(三)——对象的发布和逸出

 
并发编程原则
(三)
对象的发布和逸出
  言:
在本篇文章中我们将继续讨论有关并发编程中的其他方面的问题,这里我们主要讨论对象的发布和逸出。这对于多线程并发环境下,线程安全极为重要。往往一些问题的发生都是由于不正确的发布了对象造成了对象逸出而引起的,因此如果系统开发中需要发布一些对象,必须要做到安全发布,以免造成安全隐患。
1 )、发布和逸出:
  所谓发布对象是指使一个对象能够被当前范围之外的代码所使用。所谓逸出是指一种错误的发布情况,当一个对象还没有构造完成时,就使它被其他线程所见,这种情况就称为对象逸出。在我们的日常开发中,经常要发布一些对象,比如通过类的非私有方法返回对象的引用,或者通过公有静态变量发布对象。如下面这些代码所示:
public class Unsafepublish{
public static Set secrect;
public String[] states={“AK”,”AL”};
public void init(){
   secrect=new HashSet();
}
public String[] getStates(){
 return states;
}
}
以上代码通过 public 访问级别发布了类的域,在类的外部任何线程都可以访问这些域,这样发布对象是不安全的,因为我们无法假设,其他线程不会修改这些域,从而造成类状态的错误。还有一种逸出是在构造对象时发生的,它会使类的 this 引用发生逸出,从而使线程看到一个构造不完整的对象,如下面代码 3.1.1 所示:
 public class Escape{
   public Escape(EventSource source){
     source.registerListener(new EventListener(){
                                   public void onEvent(Event e){
                                     doSomsthring(e);
                                   }
                                });
   }
}
内部类的实例包含了外部类实例的隐含引用,在内部类会启动事件监听线程,那么事件监听线程会在外部对象还未构造完成时就会看到外部对象的 this 引用,一旦事件执行使用外部对象,很可能造成错误。无论在构造函数中隐式启动线程,还是显式启动线程,都会造成 this 引用逸出,新线程总会在所属对象构造完毕前看到它。所以如果要在构造函数中创建线程,那么不要启动它,而应该采用一个专有的 start 方法来统一启动线程。如下面 3.1.2 代码所示,采用工厂方法和私有构造函数来完成对象创建和监听器的注册 , 这样就可以避免不正确的创建。
public class Safepublish{
 private final EventListener listener;
 private Safepublish(){
listener=new EventListener(){
 public void onEvent(Event e){
    doSomething(e);
 }
};
 }
 public static Safepublish newInstance(EventSource source){
      Safepublish safe=new Safepublish();
      source. registerListener(safe. listener);
      return safe;
 }
}
2 )、安全发布对象:
   如果不正确的发布了可变对象,那么会导致两种错误。首先,发布线程以外的任何线程都可以看到被发布对象的过期值;其次更严重的情况是,线程看到的被发布对象的引用是最新的,然而被发布对象的状态却是过期的。如果一个对象是可变对象,那么它就要被安全发布,通常发布线程与消费线程必须同步化。一个正确创建的对象可以通过下列条件安全发布:
   . 通过静态初始化器初始化对象引用。
   . 将发布对象的引用存储到 volatile 域或者具有原子性的域中(如: java5.0 中的 AtomicReference )。
   . 将发布对象引用存放到正确创建的对象的 final 域中。
   . 将发布对象引用存放到由锁保护的域中(如:同步化的容器)。
如果要发布一个被静态创建的对象,最简单的方式就是使用静态初始化器,如下面代码所示: public static Holder holder=new Holder(); 静态初始化器由 JVM 在类初始化时执行, JVM 在执行静态变量的初始化时会有内在同步保护,因此可以保证对象的安全发布。
3 )、高效不可变对象:
有些对象在发布后就不会被修改,其他线程要在没有额外同步的情况下安全的访问它们,此时安全的发布就是至关重要的。所有的安全发布机制都能保证,只要一个对象在发布当时的状态对所有访问线程都可见,那么到它的引用也都可见。如果发布时的状态不会再改变,那么就必须确保任意访问是安全的。
一个对象是可变的,但是它的状态不会在发布后被修改,这样的对象称作“高效不可变对象”。这种对象没有满足我在上一篇文章中所说的不可变对象的条件,但是这些对象在发布后可以被简单的当做不可变对象来使用,另外由于减少了同步,使用它们还会提高效率。如下面代码所示:
public Map<String,Date> lastlogin=
Collections.synchonizedMap(new HashMap<String,Date>());
Date 对象本身是可变的,每当 Date 被跨线程来访问都要使用锁来确保访问安全。但是此时我们却可以把它当作一个不可变对象来使用,因为我们将 Date 对象置入了一个线程安全的 HashMap 容器中,此时访问这些 Date 对象值就不再需要额外的同步了。因此任何线程都可以在没有额外同步的情况下安全的使用一个高效不可变对象,但前提是这些对象必须被安全发布,即必须满足上面提到的安全发布条件。
4 )、安全的共享对象:
   现在我们综合前面两篇文章来总结一下,在并发编程中的一些安全共享对象的策略。
1、 线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改。
2、 共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。
3、 线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它。
4、 被守护对象:被守护对象只能通过获取特定的锁来访问。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值