并发编程之安全发布对象

 对象发布(publish)

       发布一个对象是使它能够在被当前范围之外的代码所使用。比如创建一个对象之后,提供一个非私有方法返回这个对象的引用,或者把它传递到其他类的方法中。下面是常见的发布对象的例子:

1.把对象的引用保存到静态公有域中
public static List<Object> objectList;
 public void initList() {
      objectList =New ArrayList<Object>();
}

    在上面的代码中,任何类和线程都可以访问objectList,当objectList通过initList方法实例化一个ArrayList对象后,并把引用赋值给objectList,那么这个ArrayList对象就发布出去了。同时发布一个对象还可能间接的发布了其他对象。比如objectList添加了一个object1,那么object1也被发布出去了。因为任何类或线程都可以通过遍历objectList访问并修改object1。

 

2.通过非私有方法把对象发布出去
     private String[] strArray = {"data1","data2"};
     public String[] getStrArray () {
           return strArray ;
     }
     

   通过getStrArray方法,任何类或者线程都可以访问和修改strArray,此时strArray对象也被发布出去了。但是这里strArray变量定义为私有对象,通过getStrArray方法,使得strArray超出了它的作用范围,由私有变成公有的了。

   发布一个对象,同样发布了该对象的所有非私有域引用的对象,甚至整个非私有域的引用链中的对象,都发布出去了。

对象逸出(escape)

    一个对象超出了她应有的作用域,或者未被构造完全就发布了,称为对象逸出。很多情况下我们不希望对象以及对象的内部属性不被暴露出去,但是在其他一些情况中,我们又必须使对象发布出去,但是如果不经意中把对象的内部状态也发布出去了,就使得其他线程可以修改这个对象的内部状态,造成不可预知的线程安全性风险,同时,如果对象没有构造完全就发布出去了同样会危及线程安全。上面第2个代码就是把私有对象发布出去而导致对象逸出。下面是一种对象未构造完全就发布造成的对象逸出:

对象在构建期间逸出
public class ThisEscape() {
     public ThisEscape(EventSource event) {
        event.register(new EventListener(){
            public void onEvent(Event e){ 
                  doSomeThing(e);}
         })     
    }
}

      这段代码的本意是注册监听器,通过匿名内部类发布EventListener,但是由于内部类会持有封装它的外部类的引用this,所以ThisEscape的一个对象也发布出去了。从而导致对象未构造完全就发布出去的逸出。对象只有在构造函数返回后才处于可预知的,真正稳定的状态。所以从构造方法中发布对象,它发布的对象是未构建全的对象,即使是在对象的最后一行发布也是如此。一个非常常见的在构造期对象逃逸的错误,是在构造期启动线程,无论是显式的还是隐式的(因为thread或ruanable是所属对象的内部类),this引用总是被新线程所持有。

如何安全发布对象

     为了安全的发布对象,对象的引用以及对象的状态必须同时对其他线程可见。那么如何才能安全的发布对象呢,下面是正确发布对象的几种方法

1.通过静态初始化器初始化对象的引用

  常见的例子是在构造方法中注册监听器或启动线程。如果非要在构造函数中启动线程或启动线程,可以使用私有的构造函数和公有的工厂方法,比如上面注册监听器的例子:

public class SafeListener() {
     private  final EventLister listener;
     
      private SafeListener() {
            listener= new EventLister () {
                    public void onEvent(Event e) {
                            doSomething(e);
                   }
              }
               
      }
      
      public static SafeListener getInstance(EventSource source) {
            SafeListener  safeListener = new SafeListener();
            source.resgister(safeListener );
            return safeListener ;
      }
       
}

 2.线程封闭

      需要线程同步的基础是线程间共享对象和数据,如果线程不共享数据和对象,对象只封闭在线程内部,那么就不需要线程同步,也就不需要发布对象,即使对象是不线程安全的。

2.1 线程限制

      通过编程人员维护对象安全发布,而没有变量修饰词或线程本地变量把对象限制在线程上。这种方法非常容易出错,因此也叫非正式线程限制(Ad-hoc线程限制)。下面是几种常见的线程限制方法:

volatile变量

一张典型的线程限制是使用volatile关键词。只要你确保只要一个线程修改volatile变量,那么volatile变量的读取-修改-写入操作就限制在一个线程中,避免了线程的竞争条件。并且volatile的可见性可以保证其他线程能看到变量的最新值,因此是线程安全的。

栈限制

    即使变量的作用域只局限于线程内部,比如局部变量。

2.3 threadlocal

      threadlocal允许每个线程与对象关联在一起,它提供了set/get方法,为每个使用它的线程维护一份单独的拷贝。threadlocal通常使用在防止基于可变的单例或全局变量中出现不正确的共享。

2.4 不变对象

     不变对象一旦被创建就不能更改,所以永远是线程安全的。所谓的不变对象,必须满足3个条件:创建后状态不能更改,所有域必须是final的,被正确构建(构造期间没有发生对象逸出)。不变对象一般是一些比较简单的对象,它的内部状态在对象构造方法里就赋值了。

3.将对象的引用存储到被正确同步的变量或对象中

   具体有下面几种做法:

   将对象的引用存储到volatile域或AutomaticReference中;

   将对象的引用存储到正确创建对象的final域中

   将对象的引用存储到被锁正确保护的域中,比如线程安全的容器 vector,synchronizedMap,concurrentMap,synchronizedList,synchronizedSet,BlockingQueue,concurrentLinkedQueue等。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值