java并发编程笔记day3

第四章 组合对象

4.1 设计线程安全的类

在没有进行全局检查的情况下,封装能保证线程的安全性。

  • 设计线程安全类的过程包括一下三个基本要素:

    • 1确认对象的状态由哪些变量构成的
    • 2 确定限制状态变量的不变约束
    • 3 制定一个管理并发访问对象状态的策略
  • 同步策略定义了对象如何协调对其状态的访问,并且不会违反它的不变约束或后验条。

4.1.1 收集同步需求

维护类的线程安全就意味着确保在并发访问的情况下,保护它的不变约束,这需要对其状态进行判断。对象与变量拥有一个状态空间:即它们可能处于状态的范围。状态空间越小,越容易判断它们。尽量使用final类型的域,可以简化我们的判断。

  • 很多类可以通过不变约束来判定一中状态是合法的还是非法的。

  • 一个类的不变约束也可以约束多个状态变量。当不变约束涉及多个变量的时候,任何一个操作在访问相关变量期间,线程必须占有保护这些变量的锁。

4.1.2 状态依赖的操作

若一个操作存在基于状态的先验条件,则称它为状态依赖的。

在单线程,如果操作无法满足状态依赖,则必然失败。但是在并发程序中,原本为假的先验条件可能会由于其他线程的活动而变成真。并发程序有这种可能:持续等待,直到先验条件为真,在继续处理操作。

在java中,等待特定条件成立的内置高效机制:notify和wait 与内部锁紧密的绑定在一起,因为想正确的使用它们并不容易。

创建一个条件,让它执行前必须等待先验条件为真,不如使用现有类库来提供期望的状态依赖行为更容易,比如阻塞队列或信号量,以及其他同步工具。

4.1.3 状态所有权

所有权意味着控制权。
很多情况下:所有权和封装性总是在一起出现的:

  • 对象封装它有的状态;
  • 拥有它封装的状态;

4.2 实例限制

  • 将数据封装在对象内部,把对数据的访问限制在对象的方法上,更易确保线程在访问数据的时候纵能获得正确的锁。
  • 通过使用实例控制,封装简化了类的线程安全工作,这通常称之为限制。

  • 被限制的对象一定不能逸出到它的期望可用范围之外,可以把对象限制在类实例(比如私有的类成员) 语法范围(本地变量) 或线程(对象在线程内部从一个方法传递到另一个方法,前提是该对象不被跨线程共享)中,对象不会自发的溢出自己。

  • 如下代码,非线程安全的myset管理者personset的状态,但是myset是私有的,不会溢出,想访问只能通过personset这个类的addPerson和containsPerson来访问,此时需要获得PersonSet的锁,PsersonSet的内置锁保护了它所有的状态,因而确保了PersonSet是线程安全的:
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);
    }

    interface Person {
    }
}
  • 实例构建是构建线程安全最简单的方法之一。允许不同的所保护不同的状态变量。
  • 发布其他对象,比如迭代器中或者内部类实例,可能会间接地发布受限对象,这样受限对象同样会溢出。

4.2.1 java 监视器模式

线程限制的直接推论之一就是java监视器模式 , 遵循java监视器模式的对象封装了所有的可变状态,并由对象自己的内部锁保护。
私有锁保护状态:使用私有锁对象,而不是对象的内部锁,有很多好处:比如私有对象的锁可以封装锁,这样客户代码无法获得它。
示例代码如下:

public class PrivateLock {
    private final Object myLock = new Object();
    @GuardedBy("myLock") Widget widget;

    void someMethod() {
        synchronized (myLock) {
            // Access or modify the state of widget
        }
    }
}

4.2.2 范例:机动车追踪器

每个机动车都有一个String标识,一个与之对应的位置(x,y),每个VehicleTracker对象都封装了一辆已知机动车的标识(identity)和位置(localtinon)

Map<String,Point> locations = vehicles.getLocaltions();
for(String key :locations.keySet() ){
    renderVechicle(key,localtion.getKey());
}

类似的,更新线程会在GPS设备上获取最新的数据或者手动修改数据修改机车的位置

void vehicleMoved(VehicleMovedEvent evt){
    Point loc = evt.getNewLocaltion();
    vehicles.setLocaltion(evt.getVehicleId(),loc.x,loc.y);
}

因为视图线程和更新线程是会并发的访问数据模型,因此模型必须是线程安全的。

java监听器机动车追踪器的实现:

 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描述机车位置:

@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;
    }
}

虽然这里的MutablePoint 不是线程安全的,但是MonitorVehicleTracker类是的。
我们需要将机车的位置数据返回给调用者时,正确的返回值是从MutablePoint执行拷贝的构造函数或者deepCopy 方法拷贝出来的,deepCopy会创建一个新的map。数据量小的时候性能问题不会太大,多了的话会影响性能。这个也会造成一个新的问题,那就是即使真是的locations已经发生改变,返回的拷贝的数据仍然没有变化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值