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;

    }

}

时间: 2024-08-08 17:48:57

Java并发编程 - 如何进行安全发布的相关文章

《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性

这章的主要内容是:如何共享和发布对象,从而使它们能够安全地由多个线程同时访问. 内存的可见性 确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化. 上面的程序中NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值.一种更奇怪的现象是NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为"重排序".多线程之指令重排序 失效数据 简而言之就是在缺乏同步的程序中可能会读取到

Java并发编程 Volatile关键字解析

volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 2)禁止进行指令重排序. 根据volatile的语义,我们可以看到,volatile主要针对的是并发三要素(原子性,可见性和有序性)中的后两者有实际优化作用. 可见性: 线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作.

《Java并发编程实战》第十六章 Java内存模型 读书笔记

Java内存模型是保障多线程安全的根基,这里仅仅是认识型的理解总结并未深入研究. 一.什么是内存模型,为什么需要它 Java内存模型(Java Memory Model)并发相关的安全发布,同步策略的规范.一致性等都来自于JMM. 1 平台的内存模型 在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏或栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证. JVM通过在适当的位置上插入内存栅栏来屏蔽在JVM与底层平台内存模型之间的

《Java并发编程实战》要点笔记及java.util.concurrent 的结构介绍

买了<java并发编程实战>这本书,看了好几遍都不是很懂,这个还是要在实战中找取其中的要点的,后面看到一篇文章笔记做的很不错分享给大家!! 原文地址:http://blog.csdn.net/cdl2008sky/article/details/26377433 Subsections  1.线程安全(Thread safety) 2.锁(lock) 3.共享对象 4.对象组合 5.基础构建模块 6.任务执行 7.取消和关闭 8.线程池的使用 9.性能与可伸缩性 10.并发程序的测试 11.显

java并发编程知识要点总结

java并发编程 一.关于并发 并发是什么? 并发是指在同一时间间隔内,有多个程序处于运行状态.当然,同一时刻只有一个程序在运行.与之对应的是并行,并行是指同一时刻有多个程序同时执行(宏观上) 为什么需要并发? 为了提高系统的资源利用率 和 吞吐量.就好比全班需要填表时,可以发给每个人,然后填完之后在收起来,这样的效率远比一个一个的发,然后等第一个人填完了再发给第二人填写要快的多 什么是线程安全? 线程作为独立调用的单位,当使用线程实现并发时,由于处理机的调度,可能存在线程安全问题.那什么是线程

java并发编程12.java内存模型

假设一个线程为变量赋值:variable = 3: 内存模型需要解决一个问题:“在什么条件下,读取variable的线程将看到这个值为3?” 这看上去理所当然,但是如果缺少同步,那么将会有许多因素使得线程无法立即甚至永远,看到另一个线程的操作结果. 如: 1.在编译器中生成的指令顺序,可以与源代码中的顺序不同,此外编译器还会将变量保存在寄存器而不是内存中: 2.处理器可以采用乱序或并行等方式来执行指令: 3.缓存可能会改变将写入变量提交到主内存的次序: 4.而且保存在处理器本地缓存中的值,对于其

《Java并发编程实战》读书笔记

Subsections 线程安全(Thread safety) 锁(lock) 共享对象 对象组合 基础构建模块 任务执行 取消和关闭 线程池的使用 性能与可伸缩性 并发程序的测试 显示锁 原子变量和非阻塞同步机制 一.线程安全(Thread safety) 无论何时,只要多于一个线程访问给定的状态变量.而且其中某个线程会写入该变量,此时必须使用同步来协助线程对该变量的访问. 线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的. 线程安全的实例: (1).一个无状

Java并发编程的艺术——互动出版网

这篇是计算机类的优质预售推荐>>>><Java并发编程的艺术> 阿里系和1号店资深技术专家撰写,Java并发编程领域的扛鼎之作,内容在InfoQ等社群得到高度认可,从JDK源码.JVM.CPU等多角度全面剖析与讲解Java并发编程的框架.原理和核心技术 编辑推荐 阿里系和1号店资深技术专家撰写,Java并发编程领域的扛鼎之作 内容在InfoQ等社群得到高度认可,从JDK源码.JVM.CPU等多角度全面剖析与讲解Java并发编程的框架.原理和核心技术 前言 为什么要写这本

《Java并发编程实战》/童云兰译【PDF】下载

<Java并发编程实战>/童云兰译[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062521 内容简介 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册.书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险.构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子