关于设计线程安全的类你所要知道的一切

设计线程安全的类

设计安全的类的过程,需要包含三个要素:

  • 找出构成对象状态的所有变量
  • 找出状态变量的约束
  • 建立对象状态的并发访问策略

这三条也是本篇文章的核心,以下将会逐一展开对象状态和约束都是什么,以及重点,如何结合三条要素,去设计并发访问策略。有三种常用的模式,封闭实例,委托,拓展。

状态和约束

状态就是对象中的状态变量,约束则是涉及到并发不可变性的种种约束。之所以放在一起讲,是因为本身约束就是作用于变量上。
找到状态和约束的过程,实际上也是收集同步需求的过程。

状态

对象的所有域都可能是状态,只要它们不是不可变的,如果域为引用类型,则引用对象的域也需要进行分析。

所以每个对象都会有一个状态变化空间,空间越小,这种分析越容易。从实践的角度来看,final的类型越多,不可变状态越多,这种分析越容易。

约束

状态之上的约束有三种:

  • 前置:先验条件。例如,判断集合为空才可以操作。
  • 后置:后验条件。例如,如果当前状态为14那么下一个状态必定是15。
  • 不变性条件。例如,NumberRange中low < high。

这部分,我认为简单理解为状态上的约束即可。不管是多个之间的不可变性条件,或者是集合不可变等等,通通都属于状态上的约束。

封闭实例机制

封闭实例机制,就不得不说到Java监视器模式。Java监视器模式是实现封闭实例机制最常见的方式。

所谓的封闭实例机制,就是把对象所有可变状态都封装起来,并由对象自己的内置锁来保护。

它非常好理解。简单来说就是封装状态,提供受限的同步接口进行访问。例如以下代码,只要Person是线程安全的,那么mySet就是线程安全的。

@ThreadSafe
public class PersonSet {
   
    @GuardedBy("this")
    private final Set<Person> mySet = new HashSet<Person>();

    public synchronized void addPerson(Person p) {
   
        mySet.add(p);
    }

    public synchronized boolean containsPerson(Person p) {
   
        return mySet.contains(p);
    }
}

我们尝试用开篇提到的三个要素来分析以下:

  1. 状态和约束
  • mySet,添加需要线程安全
  • Person,线程安全类(假设)
  1. 同步策略
  • 使用Java监视器模式,对mySet进行封装,提供同步的添加和访问接口
  • 用this锁保护状态的访问修改。

再来看一个更加复杂的例子,这个例子将会贯穿后文。

我们需要观察一个车辆位置的集合,跟踪车辆位置,例如绘图线程专门绘制这些点。我们需要获得某个点的位置,设置某个点的位置,获得所有车辆位置的视图。

这样的需求需要线程安全性,一种基于Java监视器的实现如下。
虽然MutablePoint不是线程安全的,但是MonitorVehicleTracker是线程安全的,它包含的map和point都没有发布(使用了保护性拷贝),并不会出现一致性问题(例如if 某个状态,进入该分支后被中断,切回来发现状态变动,导致一致性问题)。

@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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值