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

发布(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;
    }
}

Java - 如何进行安全发布的更多相关文章

  1. 谈谈JAVA中的安全发布

    谈谈JAVA中的安全发布 昨天看到一篇文章阐述技术类资料的"等级",看完之后很有共鸣.再加上最近在工作中越发觉得线程安全性的重要性和难以捉摸,又掏出了<Java并发编程实战& ...

  2. Java高并发--安全发布对象

    Java高并发--安全发布对象 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 发布对像:使一个对象能够被当前范围之外的对象使用. 对象逸出:一种错误的发布.当一个对象 ...

  3. 【适合公司业务】全网最详细的IDEA里如何正确新建【普通或者Maven】的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(类似eclipse里同一个workspace下【多个子项目】并存)(图文详解)

    不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...

  4. 全网最详细的IDEA里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(类似eclipse里同一个workspace下【一个子项目】并存)(图文详解)

    不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...

  5. 全网最详细的Eclipse里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(图文详解)

    不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...

  6. 全网最详细的MyEclipse里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(图文详解)

    不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在eclipse里,则是File -> new ->  ...

  7. MQTT介绍(3)java模拟MQTT的发布,订阅

    MQTT目录: MQTT简单介绍 window安装MQTT服务器和client java模拟MQTT的发布,订阅 在此强调一下mqtt的使用场景: 1.不可靠.网络带宽小的网络 2.运行的设备CPU. ...

  8. Java里观察者模式(订阅发布模式)

    创建主题(Subject)接口 创建订阅者(Observer)接口 实现主题 实现观察者 测试 总结 在公司开发项目,如果碰到一些在特定条件下触发某些逻辑操作的功能的实现基本上都是用的定时器 比如用户 ...

  9. java调用C# webService发布的接口

    java调用C# webService发布的接口 java调用C# webService方式有很多种我这里只介绍一种 首先需要引入axis的jar包 axis的maven坐标如下 <depend ...

  10. java调用oracle数据库发布WebService

    package com.hyan.service; import java.io.FileInputStream;import java.sql.Connection;import java.sql. ...

随机推荐

  1. 【QTP专题】05_参数化之Excel

    QTP使用外部Excel实现参数化主要有以下两种方式 导入到DataTable中 Syntax:DataTable.ImportSheet(FileName, SheetSource, SheetDe ...

  2. HAOI2010 订货

    题目链接:戳我 费用流. 将每天分成早上和晚上两个点.源点向早上连容量INF,费用为进货量的边.早上向汇点连容量供货量,费用0.早上向晚上连容量为S,费用为0的边.晚上向第二天早上连容量S,费用0.之 ...

  3. bzoj2705Longge的问题

    题目链接 题意很简单 $$ans=\sum_{i=1}^{n}gcd(i,n)$$ 然后推一下式子,求一下欧拉函数就好了 细节是由于$BZOJ$的评测计时策略, 不能线性筛啊$……$ 必须每个数单独筛 ...

  4. 初学C#,用vs去开始hello world!

    小弟初学c#,刚刚学会用vs来编写经典的hello,world程序,记录,并且分享给大家. 1. 用vs新建一个c#控制台程序: a. 首先打开vs,如下图所示,点击[新建项目]

  5. 跟刘欣学习造spring

    1: 读取配置文件并获取对象实例

  6. 将SQLAlchemy对象转化为dict

    需求一,将数据对象转为dict,但是不包括relation, import BaseClass #所有模型的基础类 def getDictFromObj_nr(obj): return_dict={} ...

  7. 海思3519A 移植 Qt 5.5.1

    源码下载 网址:qt-everywhere-opensource-src-5.5.1.tar.gz 配置生成MakeFile 文件 解压源码包,在源码包路径下生成配置 MakeFile : ./con ...

  8. 【手记】如果Idx/Sub字幕导不进MKVToolNix,看看是否这个原因

    用记事本之类的文本编辑器打开idx文件,看看时间轴部分是不是存在不规范的条目,比如: timestamp: :::, filepos: 注意,上述条目中,filepos:后面缺了一个空格,就这么一处问 ...

  9. php 返回数组中指定多列的方法

    php array_column 方法可以返回数组中指定的一列,但不能返回多列,本文将介绍array_column方法的使用,并用代码演示返回数组中指定多列的方法. 1.array_column说明 ...

  10. GPUImage处理图片(滤镜)

    GPUImage 是基于 GPU 处理图像的一个开源库, 提供了各种图像处理滤镜,例如调 亮度/饱和度/曝光度/白平衡/锐化等滤镜. 并且支持照相机/摄像机 的实时滤镜. GPUImage采用链式方式 ...