Java并发编程之对象的组合

目录
  • 1. 设计线程安全的类
    • 1.1 收集同步需求
    • 1.2 依赖状态的操作
    • 1.3 状态的所有权
  • 2. 实例封闭
    • 2.1 Java监视器模式
  • 3. 线程安全性的委托
    • 3.1 基于委托的车辆追踪器
    • 3.2 独立的状态变量
    • 3.3 发布底层的状态变量

1. 设计线程安全的类

在设计线程安全类的过程中,需要包含以下三个基本要素:

  • 找出构成对象状态的所有变量。
  • 找出约束变量的不变性条件。
  • 建立对象状态的并发访问管理策略。

1.1 收集同步需求

在很多类中都定义了一些不可变条件,用于判断状态是否有效。比如:计数器的取值范围上存在一个限制,就是不能是负值。在操作中还会包含一些后验条件来判断状态迁移是否有有效。比如:计数器的当前状态为8,那么下一个有效状态只能是9。由于不变性条件和后验条件在状态及状态转换上施加了各种约束,因此就需要额外的同步和封装。

1.2 依赖状态的操作

在某些对象的方法中还包含一些基于状态的先验条件。比如:不能从空队列中移除一个元素,在删除元素前,队列必须处于非空状态。如果在某个操作中包含有基于状态的先验条件,那么这个操作就称为依赖状态的操作。

1.3 状态的所有权

对象封装了它拥有的状态,那么它就拥有封装状态的所有权。状态的所有者将决定采用何种加锁协议来维持状态的完整性。所有权意味着控制权。如果发布了某个可变对象的引用,那么就不再拥有独占的控制权,最多是共享控制权。为了防止多个线程在并发访问同一个对象时产生相互干扰,这些对象应该要么是线程安全的对象,要么是事实不可变的对象,或者用同一个锁保护的对象。

2. 实例封闭

将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据是总能持有正确的锁。被封闭对象一定不能超过它们既定的作用域。对象可以封闭在类的一个实例中,比如:作为类的一个私有成员;或者封闭在某个作用域内,比如:作为一个局部变量;或者封闭在线程中,比如:在某个线程中将对象从一个方法传递到另一个方法,而不是在多个线程之间共享该对象。

封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无须检测整个程序。

2.1 Java监视器模式

遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。示例:一个用于调度车辆的“车辆跟踪器”,每台车都由一个String对象来标识,并且拥有一个对应的位置坐标(x,y)。创建一个追踪器类用来封装所有车辆的标识和位置,该类由多个线程(读取操作和更新操作)共享。

@NotThreadSafe
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;
    }
}
@ThreadSafe
public class MonitorVehicleTracker {
    @GuardedBy("this") 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);
    }
}

虽然类MutablePoint不是线程安全的,但是追踪器类是线程安全的。它所包含的Map对象和可变的Point对象都未曾发布。当需要回去车辆的位置时,通过拷贝构造函数或deepCopy方法复制正确的值,从而生成一个新的Map对象。车辆少是,这并不存在性能问,单在车辆数量非常大的情况下将极大地降低性能。

3. 线程安全性的委托

大多数对象都是组合对象。当从头开始构建一个类,或者将多个非线程安全的类组合为一个类时,Java监视器模式是非常有用的。但是,如果类中的各个组件都已经是线程安全的,那么实现线程安全就应视情况而定。

3.1 基于委托的车辆追踪器

保存车辆位置信息的Map类,使用线程安全的ConcurrentMap类代替。

使用不可变的Point类来代替MutablePoint类:

public class Point {
    public final int x, y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

在DelegatingVehicleTracker中没有使用显式的同步,所有对状态的访问都由ConcurrentHashMap来管理:

@ThreadSafe
public class DelegatingVehicleTracker {
    private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

    public DelegatingVehicleTracker(Map<String, Point> points) {
        locations = new ConcurrentHashMap<String, Point>(points);
        unmodifiableMap = Collections.unmodifiableMap(locations);
    }

    public Map<String, Point> getLocations() {
        return unmodifiableMap;
    }

    public Point getLocation(String id) {
        return locations.get(id);
    }

    public void setLocation(String id, int x, int y) {
        if (locations.replace(id, new Point(x, y)) == null)
            throw new IllegalArgumentException("invalid vehicle name: " + id);
    }
}

在使用监视器模式的车辆追踪器中返回的是车辆位置的快照,而在使用委托的车辆追踪器中返回的是一个不可修改却实时的车辆位置信息。

3.2 独立的状态变量

我们还可以将线程安全性委托给多个状态变量,只要这些变量是彼此独立的,即组合而成的类并不会在其包含的多个状态变量上增加任何不变形条件。

public class VisualComponent {
    private final List<KeyListener> keyListeners
            = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners
            = new CopyOnWriteArrayList<MouseListener>();

    public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

    public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

    public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

    public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }
}

VisualComponent类是一个图形组件,允许客户程序注册监控鼠标和键盘等事件的监听器。在鼠标事件监听器和键盘事件监听器之间不存在任何关联,二者是彼此独立的,因此VisualComponent可以将其线程安全性委托给这两个线程安全的监听器列表。

如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。

3.3 发布底层的状态变量

如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。我们来修改之前的车辆追踪器的代码,使其发布底层的可变状态。

首先写一个可变并且线程安全的Point类:

public class SafePoint {
    @GuardedBy("this") private int x, y;

    private SafePoint(int[] a) {
        this(a[0], a[1]);
    }

    public SafePoint(SafePoint p) {
        this(p.get());
    }

    public SafePoint(int x, int y) {
        this.set(x, y);
    }

    public synchronized int[] get() {
        return new int[]{x, y};
    }

    public synchronized void set(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

再修改追踪器的代码:

public class PublishingVehicleTracker {
    private final Map<String, SafePoint> locations;
    private final Map<String, SafePoint> unmodifiableMap;

    public PublishingVehicleTracker(Map<String, SafePoint> locations) {
        this.locations = new ConcurrentHashMap<String, SafePoint>(locations);
        this.unmodifiableMap = Collections.unmodifiableMap(this.locations);
    }

    public Map<String, SafePoint> getLocations() {
        return unmodifiableMap;
    }

    public SafePoint getLocation(String id) {
        return locations.get(id);
    }

    public void setLocation(String id, int x, int y) {
        if (!locations.containsKey(id))
            throw new IllegalArgumentException("invalid vehicle name: " + id);
        locations.get(id).set(x, y);
    }
}

PublishingVehicleTracker中,其线程安全性委托给底层的ConcurrentHashMap,只是Map中的元素是线程安全的可变的。getLocations方法返回底层Map对象的一个不可变副本,调用者不能增加或删除车辆,但可以通过修改返回Map中的SafePoint值来修改车辆的问题。

到此这篇关于Java并发编程之对象的组合的文章就介绍到这了,更多相关Java对象的组合内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java 中组合模型之对象结构模式的详解

    Java 中组合模型之对象结构模式的详解 一.意图 将对象组合成树形结构以表示"部分-整体"的层次结构.Composite使得用户对单个对象和组合对象的使用具有一致性. 二.适用性 你想表示对象的部分-整体层次结构 你希望用户忽略组合对象与单个对象的不同,用户将统一使用组合结构中的所有对象. 三.结构 四.代码 public abstract class Component { protected String name; //节点名 public Component(String n

  • Java并发编程之对象的组合

    目录 1. 设计线程安全的类 1.1 收集同步需求 1.2 依赖状态的操作 1.3 状态的所有权 2. 实例封闭 2.1 Java监视器模式 3. 线程安全性的委托 3.1 基于委托的车辆追踪器 3.2 独立的状态变量 3.3 发布底层的状态变量 1. 设计线程安全的类 在设计线程安全类的过程中,需要包含以下三个基本要素: 找出构成对象状态的所有变量. 找出约束变量的不变性条件. 建立对象状态的并发访问管理策略. 1.1 收集同步需求 在很多类中都定义了一些不可变条件,用于判断状态是否有效.比如

  • Java并发编程之对象的共享

    目录 1.可见性 1.1 失效数据 1.2 非原子的64位操作 1.3 加锁和可见性 1.4 volatile变量 2. 发布与泄露 3. 线程封闭 3.1 Ad-hoc线程封闭 3.2 栈封闭 3.3 ThreadLocal类 4. 不变性 4.1 final域 4.2 使用volatile类型来发布不可变对象 5 安全发布 5.1 不正确的发布 5.2 不可变对象与初始化安全性 5.3 安全发布的常用模式 5.4 事实不可变对象 5.5 可变对象 5.6 安全的共享对象 1.可见性 通常,我

  • java并发编程之同步器代码示例

    同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作.最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier和Exchanger 队列同步器AbstractQueuedSynchronizer是用来构建锁或者其他同步组件的基础框架,它内部使用了一个volatiole修饰的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作. 同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽

  • Java 并发编程:volatile的使用及其原理解析

    Java并发编程系列[未完]: •Java 并发编程:核心理论 •Java并发编程:Synchronized及其实现原理 •Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) •Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) •Java 并发编程:volatile的使用及其原理 一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchroniz

  • Java并发编程之重入锁与读写锁

    重入锁 重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁.重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问题. 1.线程再次获取锁.锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取. 2.锁的最终释放.线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁.锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放

  • Java并发编程(CyclicBarrier)实例详解

    Java并发编程(CyclicBarrier)实例详解 前言: 使用JAVA编写并发程序的时候,我们需要仔细去思考一下并发流程的控制,如何让各个线程之间协作完成某项工作.有时候,我们启动N个线程去做一件事情,只有当这N个线程都达到某一个临界点的时候,我们才能继续下面的工作,就是说如果这N个线程中的某一个线程先到达预先定义好的临界点,它必须等待其他N-1线程也到达这个临界点,接下来的工作才能继续,只要这N个线程中有1个线程没有到达所谓的临界点,其他线程就算抢先到达了临界点,也只能等待,只有所有这N

  • Java 并发编程学习笔记之Synchronized简介

    一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,

  • Java 并发编程学习笔记之核心理论基础

    并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可靠的多线程并发程序.本系列会从线程间协调的方式(wait.notify.notifyAll).Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制.在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式.实现源码及其背后的原理.本

  • Java并发编程-volatile可见性详解

    前言 要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸.最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍. 有什么用? volatile主要对所修饰的变量提供两个功能 可见性 防止指令重排序 <br>本篇博客主要对volatile可见性进行探讨,以后发表关于指令重排序的博文. 什么是可见性? 把JAVA内存模型(JMM)展示得很详细了,简单概括一下 1.每个Thread有一个属于自己的工作内存(可以理解为每个

  • Java并发编程之常用的多线程实现方式分析

    本文实例讲述了Java并发编程之常用的多线程实现方式.分享给大家供大家参考,具体如下: 概述 常用的多线程实现方式有2种: 1. 继承Thread类 2. 实现Runnable接口 之所以说是常用的,是因为通过还可以通过JUC(java.util.concurrent)包中的线程池来实现多线程.关于线程池的内容,我们以后会详细介绍:现在,先对的Thread和Runnable进行了解. Thread简介 Thread 是一个类.Thread本身就实现了Runnable接口.它的声明如下: publ

随机推荐