Java并发编程之对象的组合

 更新时间:2022年04月07日 14:50:30   作者:万猫学社  
这篇文章主要介绍了Java并发编程之对象的组合,文章基于Java的相关资料展开主题内容,具有一定的参考价值,需要的小伙伴可以参考一下

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对象的组合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解SpringBoot启动项目后执行方法的几种方式

    详解SpringBoot启动项目后执行方法的几种方式

    在项目开发中某些场景必须要用到启动项目后立即执行方式的功能,本文主要聊聊实现立即执行的几种方法,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • Lambda表达式下访问外部变量问题

    Lambda表达式下访问外部变量问题

    这篇文章主要介绍了Lambda表达式下访问外部变量问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • 微信公众号支付(一)如何获取用户openId

    微信公众号支付(一)如何获取用户openId

    本篇文章给大家介绍微信公众号支付如何获取用户openid,需要授权回调页面域名等一系列途径实现此功能,需要的朋友可以参考下
    2015-09-09
  • Spring Boot项目维护全局json数据代码实例

    Spring Boot项目维护全局json数据代码实例

    这篇文章主要介绍了Spring Boot项目维护全局json数据代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java 泛型考古 泛型擦除 包装类详细解析

    Java 泛型考古 泛型擦除 包装类详细解析

    泛型是在Java SE 1.5引入的的新特性,本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法,本篇我们一起来学习泛型考古、泛型擦除、包装类
    2022-03-03
  • Spring中的事务拦截器顺序如何控制详解

    Spring中的事务拦截器顺序如何控制详解

    这篇文章主要介绍了Spring中的事务拦截器顺序如何控制详解,那么spring事务拦截器的顺序如何控制呢,若我们自己也添加了一些拦截器,此时事务拦截器和自定义拦截器共存的时候,他们的顺序是怎么执行的?如何手动来控制他们的顺序,需要的朋友可以参考下
    2024-01-01
  • springcloud引入spring-cloud-starter-openfeign失败的解决

    springcloud引入spring-cloud-starter-openfeign失败的解决

    这篇文章主要介绍了springcloud 引入spring-cloud-starter-openfeign失败的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Springboot中RedisTemplate设置String、Hash、List过期时间

    Springboot中RedisTemplate设置String、Hash、List过期时间

    本文主要介绍了Springboot中RedisTemplate设置String、Hash、List过期时间,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • 聊聊maven与jdk版本对应关系

    聊聊maven与jdk版本对应关系

    这篇文章主要介绍了maven与jdk版本对应关系,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 浅析Java中的访问控制权限

    浅析Java中的访问控制权限

    这篇文章主要介绍了浅析Java中的访问控制权限,在Java中,提供了四种访问权限控制,分别是默认访问权限、public、private以及protected,感兴趣的小伙伴们可以参考一下
    2016-02-02

最新评论