阅读记录——多线程编程之不可变对象模式(Immutable Object)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/edc0228/article/details/81180879

摘抄:

多线程共享变量的情况下,为了保证数据的一致性,往往需要对这些变量的访问进行加锁。而锁本身又会带来一些问题和开销。不可变对象模式使得我们可以在不适用锁的情况下,既保证共享变量的线程安全,又能避免引入锁可能带来的问题和开销。

多线程环境中,一个变量常常会被多个线程共享。这种情况下,如果存在多个线程并发的修改该对象的状态或者一个线程访问对象的状态而另外一个线程试图修改该对象的状态,我们不得不做一些同步访问控制以保证数据的一致性。而这些同步访问控制 ,如显示锁(Explicit Lock) 和 CAS(Compare and Swap)操作,会带来额外的开销,如上下文切换、等待时间和ABA问题等。不可变对象模式的意图是通过使用对外可见的状态不可变的对象,使得被共享的对象“天生”具有线程安全性,而无需额外的同步访问控制。从而保证了数据一致性,又避免了同步访问控制所产生的额外的开销和问题,也简化了编程。

所谓状态不可变对象,就是对象一经创建,其对外可见的状态就保持不变,例如java 中的Integer 和 String。

举例:
一个车辆管理系统要对车辆的位置信息进行跟踪,我们可以对车辆的位置信息建立如下模型:

/**
 * 状态可变的位置信息模式(非线程安全)
 * @author JiaJiCheng
 * @date 2018年7月24日
 */
public class Location {
    // 经度
    private double x;

    // 维度
    private double y;

    public Location(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public void setXY(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

当系统收到新的车辆坐标数据时,需要调用Location的setXY方法来更新位置信息。显然,上述代码中的setXY方法是非线程安全的,因为对坐标x和y的写操作不是一个原子操作。
(补充说明:什么是原子操作?原子操作指的是相应的操作是单一不可分隔的操作。如:对int型变量count执行counter++的操作就不是原子操作。这是因为counter++实际上可以分解为3个操作 1)读取counter的当前值 2)拿counter当前值和1做加法运算 3)将做过加法运算后的值赋值给counter变量。)
当setXY方法被调用时,如果在x写入完毕,而y在写入前有其他线程来读取位置信息,则该线程可能读取到一个被追踪车辆根本不曾经过的位置。为了使setXY方法具备线程安全性,我们需要借助锁进行访问控制。虽然被追踪车辆的位置总是在变化,但是我们也可以将位置信息建模为状态不可变的对象如下:

public final class Location {
    // 经度
    private final double x;

    // 维度
    private final double y;

    public Location(double x, double y) {
        this.x = x;
        this.y = y;
    }

}

使用状态不可变的位置信息模型时,如果车辆的位置发生变动,则更新车辆的位置信息是通过替换整个表示位置信息的对象来实现的。

/**
 * 车辆跟踪
 * @author JiaJiCheng
 * @date 2018年7月24日
 */
public class VehicleTracker {
    // 使用线程安全的map,KEY = 车辆的id, VALUE = 车辆位置
    private Map<String, Location> locMap = new ConcurrentHashMap<>();

    /**
     * 更新车辆的位置信息
     * @param vehicleId   车辆的id
     * @param newLocation  车辆位置
     */
    public void updateLocation(String vehicleId, Location newLocation) {
        locMap.put(vehicleId, newLocation);
    }
}

以上代码使用了线程安全的map维护了被追踪车辆的位置信息,每次更新的位置都是一个全新的Location对象。

一个严格意义上的不可变对象要满足以下条件:
1.类本身使用final修饰,防止其子类改变其定义的行为;
2.所有字段都是用final修饰;
3.在对象创建的过程中,this关键字没有泄露给其他类:防止其他类(如该类的匿名内部类)在对象创建过程中修改其状态;
4.任何字段,若其引用了其他状态可变的对象(如集合、数组等),则这些字段需要是private修饰的,并且这些字段值不能对外暴露。若有相关方法要返回这些字段的值,应该进行防御性复制(Defensive Copy)。

猜你喜欢

转载自blog.csdn.net/edc0228/article/details/81180879