java并发编程-对象的发布和逸出

对象发布:

       发布(publish)对象意味着其作用域之外的代码可以访问操作此对象。例如将对象的引用保存到其他代码可以访问的地方,或者在非私有的方法中返回对象的引用,或者将对象的引用传递给其他类的方法。
为了保证对象的线程安全性,很多时候我们要避免发布对象,但是有时候我们又需要使用同步来安全的发布某些对象。

逸出:

      逸出即为发布了本不该发布的对象;


逸出的方式:

 1、 静态变量的引用对象:

  将对象的引用保存在一个公有的静态变量中,是发布对象的最直接和最简单的方式。

   例如以下代码示例,在示例中我们也看到,由于任何代码都可以遍历我们发布persons集合,导致我们间接的发布了Person实例,自然也就造成可以肆意的访问操作集合中的Person元素。

public class ObjectPublish {
 
    public static HashSet<Person> persons ;
    public void init()
    {
        persons = new HashSet<Person>();
    }
}

  这样任何代码都可以遍历这个集合,并获得对这个新Person对象引用。


 2、非私有的方法返回对象的引用:

  在非私有的方法内返回一个私有变量的引用会导致私有变量的逸出,例如以下代码

public class ObjectPublish {   
    private  HashSet<Person> persons=  new HashSet<Person>();
    public HashSet<Person>  getPersons()
    {
        return this.persons;
    }
}

  在这个实例中,该变量本是私有的变量,但是这样已经逃出它所在的作用域范围,因为任何调用者都能修改这个集合里面的内容

 3、发布一个对象也会导致此对象的所有非私有的字段对象的发布 (this逃逸):

       this逃逸就是说,在构造函数返回之前,其他线程就已经取得了该对象的引用,由于构造函数还没有完成,所以,对象也可能是残缺的,所以,取得对象引用的线程使用残缺的对象极有可能发生错误的情况。因为这两个线程是异步的,取得对象引用的线程并不一定会等待构造对象的线程完结后在使用引用。

     stackoverflowhttp://stackoverflow.com/questions/3705425/java-reference-escape讨论了this逸出的问题。下面给出2个具体点的例子,更容易理解this逸出的问题。

public class ThisEscape {  
    private final int var;  
  
    public ThisEscape(EventSource source) {  
        source.registerListener(  
            new EventListener() {  
                public void onEvent(Event e) {  
                    doSomething(e);  
                }  
            }  
        );  
  
        // more initialization  
        // ...  
  
        var = 10;  
    }  
  
    // result can be 0 or 10  
    int doSomething(Event e) {  
        return var;  
    }  
}

   事件监听器一旦注册成功,就能够监听用户的操作,调用对应的回调函数。比如出发了e这个事件,会执行doSomething()回调函数。这个时候由于是异步的,ThisEscape对象的构造函数很有可能还没有执行完(没有给var赋值),此时doSomething中获取到的数据很有可能是0,这不符合预期。

结论 :

     1 就是不要在 建构函数随便创建匿名类然后 发布它们。

     2,不用再建构函数中随便起线程, 如果起要看有没有发布匿名类对象。

 

避免对象发布方式:

  1、线程封闭:

         线程封闭可以使数据的访问限制在单个线程之内,相对锁定同步来说,其实实现线程安全比较简单的方式。
        java提供了ThreadLocal类来实现线程封闭,其可以使针对每个线程存有共享状态的独立副本。其通常用于防止对可变的单实例变量和全局变量进行共享,例如每个请求作为一个逻辑事务需要初始化自己的事务上下文,这个事务上下文应该使用ThreadLocal来实现线程封闭。
  2、栈封闭:

        栈封闭是线程封闭的特例,即数据作为局部变量封闭在执行线程中,对于值类型的局部变量不存在逸出的问题,如果是引用类型的局部变量,开发人员需要确保其不要作为返回值或者其他的关联引用等而被逸出。

  3、使用不可变的对象

       首先对于不变性的定义:某个对象创建之后就不能修改其状态,那么我们就说这个对象是不可变对象,不变形并不等于将对象中所有的域都声明为 final类型,即时对象中所有的域都是final类型,这个对象也不是不变的,因为有可能在final类型域中保存了可变对象的引用。

要保证不变性对象的条件:

    1、对象创建之后任何状态都不能修改

    2、对象的所有字段都用final标记

    3、对象不存在this隐式构造函数逸出

 如:  

public final class ThreeStooges {
    private final Set<String> stooges = new HashSet<String>();
 
    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }
 
    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
 
    public String getStoogeNames() {
        List<String> stooges = new Vector<String>();
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
        return stooges.toString();
    }
}

怎么进行对象安全发布:

     很多时候我们是希望在多线程之间共享数据的,此时我们就必须确保安全的发布共享对象。要安全的发布一个对象,对象的引用以及对象的状态对其他线程都是可见的,一个正确构造的对象可以通过以下方式安全的发布

  1、 在静态构造函数中初始化对象引用

      private staits  Person persons=  new Person();

       静态初始化器有JVM在类初始化阶段执行,由于JVM内部存在同步机制。

  2、使用volatile和AtomicReferance限定对象引用

 3、使用final限定对象引用
  4、将对象引用保存到有锁保护的字段中

 在Java安全库中也提供了安全发布的保证:

   1、通过Vector、CopyOnwriterArrayList/CopyOnwriterArraySet/ sychronizedList等安全容器中,或者有锁的容器(Hashtable vector)存放元素。

    2、通过BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全的发布到任何从这些队列中访问该元素的线程。

参考资料:

   <<Java并发编程实战书籍>>



发表评论