美文网首页
1_基础知识_chapter03_对象的共享_2_发布与逸出

1_基础知识_chapter03_对象的共享_2_发布与逸出

作者: 米都都 | 来源:发表于2019-01-21 11:13 被阅读0次
    • 发布: 使一个对象能够在当前作用域之外的代码中使用

      逸出: 某个不应该发布的对象被发布

    • (1) 发布对象最简单的方式是将对象的引用保存在一个public static变量中, 这样任何类和线程都能看见该对象

      (2) 发布某个对象时, 可能会间接发布其他对象

      例如, 一个容器中装着很多个引用类型,这些引用类型的对象都会被发布

      (3) 发布一个对象时, 在该对象的非private的引用的所有对象都会被发布

      例如

         class Point {
      
             public Integer x;
             public Integer y;
         }
      
         class Line {
      
             private Point pointA;
             private Point pointB;
      
             public Point getPointA() {
      
                 return this.pointA;
             }
         }
      

      当调用line.getPointA()时, 不光line对象的pointA对象会被发布, 由于Point类中的x和y都不是private的,所以pointA.x和pointB.y两个Integer对象也会被发布

      (3) 当把一个对象传递给某个外部方法时, 就相当于发布了这个对象

      外部方法: 对于某个类C, 外部方法指的是其他类中定义的方法 + 当前类中可以被改写的方法(既不是private也不是final的方法)

      (4) 最后一种发布对象的机制是发布一个内部的类实例

      因为内部类自动持有外部类的this引用(包括了内部嵌套了几层的内部类也会持有最外部类的this引用)

    • 不要在构造过程中使得this引用逸出!!!

      (1) 当从对象的构造函数中发布对象时, 只是发布了一个尚未构造完成的对象, 即使发布对象的语句位于构造函数的最后一行(因为还有指令重排序的问题)

      错误示例

        class EventSource {
      
            private EventListener eventListener;
      
            void registerListener(EventListener eventListener) {
                this.eventListener = eventListener;
            }
      
            public void onEvent(Event e) {
      
                while (this.eventListener == null) {
                    Thread.yield();
                }
      
                this.eventListener.onEvent(e);
            }
        }
      
        interface EventListener {
            void onEvent(Event e);
        }
      
        interface Event {
        }
      
      
        class EventSourceThread implements Runnable {
      
            private EventSource eventSource;
      
            public EventSourceThread(EventSource eventSource) {
      
                this.eventSource = eventSource;
            }
      
            @Override
            public void run() {
      
                eventSource.onEvent(new Event() {
                });
            }
        }
      
        public class ThisEscape {
      
            private final int id;
            private final String name;
      
            public ThisEscape(EventSource source) {
      
                this.id = 1;
      
                source.registerListener(new EventListener() {
      
                    public void onEvent(Event e) {
      
                        doSomething(e);
      
                        System.out.println(ThisEscape.this.id);
                        System.out.println(ThisEscape.this.name);
                    }
                });
      
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
      
                this.name = "ThisEscape";
      
                System.out.println("ThisEscape object's initialization completed!");
            }
      
            private void doSomething(Event e) {
            }
        }
      
        class Test {
      
            public static void main(String[] args) {
      
                EventSource eventSource = new EventSource();
      
                Thread thread = new Thread(new EventSourceThread(eventSource));
                thread.start();
      
                ThisEscape thisEscape = new ThisEscape(eventSource);
            }
        }
      

      输出的结果是

        1
        null
        ThisEscape object's initialization completed!
      

      原因是现在有两个线程: 主线程和EventSourceThread线程, EventSourceThread线程等待着ThisEscape的实例构造, 由于使用了内部类天然获得了外部类的this引用, 所以ThisEscape对象并未完成构造函数, 就已经注册到了eventSource对象里面,EventSourceThread线程就可以执行onEvent()方法。而由于ThisEscape只构造了一半, 所以输出了正确的id和不正确的name(事实上由于指令重排序,id也有可能不正确)。这就造成了this引用逸出

      (2) 另一种常见this引用逸出的情形是在构造函数中启动新线程。即构造函数中创建新线程,新线程已经可以看到this引用, 在未完全构造之前, this引用已经在新线程中启动使用了.

      (3) 这类问题的引发条件是

      1° 使用了内部类

      2° 在构造函数中就把这个内部类给发布了出去

      解决办法就是不要在构造函数中将内部类发布出去。对于(1)中的错误类型,可以使用private构造函数将函数构造完整后,再注册,再使用一个public static的工厂方法获得实例对象; (2)线程在构造函数中的情况,解决思路类似,就是可以在构造函数中将新线程和对象耦合,但是t.start()放在构造函数外面

      正确示例

        interface EventSource {
            void registerListener(EventListener e);
        }
      
        interface EventListener {
            void onEvent(Event e);
        }
      
        interface Event {
        }
      
        public class SafeListener {
      
            private final EventListener listener;
      
            private SafeListener() {
      
                listener = new EventListener() {
                    public void onEvent(Event e) {
                        doSomething(e);
                    }
                };
            }
      
            public static SafeListener newInstance(EventSource source) {
        
                // 先完整构造        
                SafeListener safe = new SafeListener();
        
                // 再注册耦合
                source.registerListener(safe.listener);
      
                // 返回完整实例对象
                return safe;
            }
      
            private void doSomething(Event e) {
            }
        }
      
    • 对于服务器应用程序,无论开发阶段还是测试阶段,都应该加上 -server参数深度优化,因为server深度优化指令重排序优化会更多更明显,这样在开发过程中就会出现问题,而不是运行发布后才出现

    相关文章

      网友评论

          本文标题:1_基础知识_chapter03_对象的共享_2_发布与逸出

          本文链接:https://www.haomeiwen.com/subject/hbaqjqtx.html