Java - 如何进行安全发布

首先让我简单解释一下所谓"发布"。

发布(publish),使对象可以在当前作用域之外的代码中可见,如果该对象被发布,则该对象的非私有域中引用的所有实例同样也会被发布。

不仅仅是作为一个field,当一个对象作为一个方法的参数或者在公有方法中作为返回引用,这都属于发布。

而相对地,对于错误的发布,我们将其称为逸出(escape)。

那么,什么是"错误的发布"? 比如发布导致封装性的破坏(可能直接导致无法安全地进行继承)、线程安全性问题(尤其是不变性条件的破坏)。

仅仅是修改了访问修饰,但可能导致难以预测的问题,并发编程时发布也变得尤为敏感。

 

 

那如何能避免逸出? 最简单的方法就是不发布。

线程封闭 -> http://alvez.blog.51cto.com/7711135/1549674

 

 

但总不能一直这样下去,资源的共享也是线程并发的一大优势,于是如何进行安全的发布显得非常重要。

那么不可变对象的发布是否也属于发布? 当然,这也是安全发布的一种策略。

(保证不可变 -> http://alvez.blog.51cto.com/7711135/1549811)

任何线程都可以在没有进行额外同步处理的情况下安全访问不可变对象。

但是不可变并不仅仅是final关键字那么简单,如果指向的对象是可变的则仍需要进行同步处理。

 

 

看一段代码,如果只是单线程应用,则几乎没有问题(其实问题还是有的),但是从并发的角度看,发布出来的holder对象甚至没有考虑可见性问题,而且对象尚未创建完成就已经发布,其他线程看到这个holder时将是不一致状态的holder:

1
2
3
4
5
6
7
public  class  StuffIntoPublic {
     public  Holder holder;
 
     public  void  initialize() {
         holder =  new  Holder( 42 );
     }
}

 

 

于是,为了应对状态不一致的情况,我们将Holder设计为...谁会想用这样的对象...

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Holder {
     private  int  n;
 
     public  Holder( int  n) {
         this .n = n;
     }
 
     public  void  assertSanity() {
         if  (n != n)
             throw  new  AssertionError( "This statement is false." );
     }
}

 

 

既然如此,那如何安全并友好地对可变对象进行同步? 以下是几点建议:

  1. 使用静态初始化方法创建对象。

  2. 用volatile或者AtomicReference修饰对象,保证并发可见性。

  3. 使用锁进行保护。

  4. 用final修饰,即便不能保证不可变,也可以保证安全初始化,并且更易分析。

 

 

以下面的代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  public  class  MonitorVehicleTracker {
     private  final  Map<String, MutablePoint> locations;
 
     public  MonitorVehicleTracker(Map<String, MutablePoint> locations) {
         this .locations = deepCopy(locations);
     }
 
     public  synchronized  Map<String, MutablePoint> getLocations() {
         return  deepCopy(locations);
     }
 
     public  synchronized  MutablePoint getLocation(String id) {
         MutablePoint loc = locations.get(id);
         return  loc ==  null  null  new  MutablePoint(loc);
     }
 
     public  synchronized  void  setLocation(String id,  int  x,  int  y) {
         MutablePoint loc = locations.get(id);
         if  (loc ==  null )
             throw  new  IllegalArgumentException( "No such ID: "  + id);
         loc.x = x;
         loc.y = y;
     }
 
     private  static  Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) {
         Map<String, MutablePoint> result =  new  HashMap<String, MutablePoint>();
 
         for  (String id : m.keySet())
             result.put(id,  new  MutablePoint(m.get(id)));
 
         return  Collections.unmodifiableMap(result);
     }
}

整个对象中只有一个locations,我们使用final修饰保证了其安全创建。

但只是这一点还不够,构造器中我们并没有直接将参数引用到location上,而是进行一次静态的deep copy,并使用Collections.unmodifiableMap将结果装饰一遍。

 

接着,我们在getter/setter中做了同步处理,这一点正是OO特性对并发良好支持的体现。

getLocation中我们获得location后并没有直接将其返回,而是重新创建一个新对象,以此防止逸出。

 

 

但是这并不成功,问题不在于这段代码,而是在于locations的泛型——MutablePoint上:

1
2
3
4
5
6
7
8
9
10
11
12
13
public  class  MutablePoint {
     public  int  x, y;
 
     public  MutablePoint() {
         x =  0 ;
         y =  0 ;
     }
 
     public  MutablePoint(MutablePoint p) {
         this .x = p.x;
         this .y = p.y;
     }
}

由于locations中的元素存在问题,这样一来locations就不能算是安全的发布。

 

 

一个对象被发布,该对象中非私有的引用也会被发布,因此,对point也需要进行处理。

鉴于point的创建开销不大,我们只需要保证其不可变:

1
2
3
4
5
6
7
8
public  class  Point {
     public  final  int  x, y;
 
     public  Point( int  x,  int  y) {
         this .x = x;
         this .y = y;
     }
}

转载于:https://www.cnblogs.com/kavlez/p/4041397.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值