Java Concurrency in Practice 4.3-4.5相关问题及理解

1.线程安全性的委托

委托意味着,将线程安全性委托给自己的线程安全组件,但请一定注意,即便组件都是线程安全的,也不能保证类是线程安全的!上篇博文提到了“车辆追踪”的时效性问题,那么如何解决?既然问题是返回的结果为locations的一个deepcopy,那么解决办法当然是发布一个线程安全的Map对象,不需要进行复制,代码如下:

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

    public DelegatingVehicleTracker(Map<String, Point> points) {
        locations = new ConcurrentMap<>(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);
        }
    }
}

这里,注意到locations使用了ConcurrentMap这个线程安全的Map,使用装饰器模式,将它转换为不可修改的Map后,发布没有任何线程安全问题,而且也解决了车辆位置的时效性问题:因为发布的是实时locations。这里的Point类也是线程安全的,可以直接通过getLocation()方法发布:

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

这里,因为属性x, y都是final的,所以在初始化之后是不可改变的,不会出现状态不一致的情况,所以为线程安全的。final域能保证在对象引用在任意线程可见之前,final域已经被初始化过了,不会出现中间状态这种情况。当然,如果你想将Point改为可变的线程安全类也可以:

public class SafePoint {
    private SafePoint(int[] a) { this(a[0], a[1]); }
    
    public SafePoint(SafePoint p) { this(p.get()); }
    
    public SafePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

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

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

注意,这里使用数组保存坐标,是为了使“修改”这一操作具有原子性,不能出现x坐标和y坐标修改出现不一致性。

2.在线程安全类中添加功能

一般来说,你不能直接修改原始的类(无法访问到源代码),这时如果你想添加线程安全的功能就要格外小心,不能破坏原有类的线程安全特性。看一个例子,假设我要添加一个“如果不存在则添加”的操作,这个操作一般分为两个步骤:先判断是否存在,再往里添加元素。如果不满足线程安全的要求,则可能出现在容器中保留两个相同的元素。最简单的做法,去扩展一个线程安全类:

public class BetterVector<E> extends Vector<E> {
    public synchronized boolean putIfAbsent(E e) {
        boolean absent = !contains(e);
        if(absent) 
            add(x);
        return absent;
    }
}

但这这种做法通常是不被推荐的,原因在于你将同步策略分布到多个单独维护的源代码文件中,如果底层改变了同步策略,那么子类的同步性会被破坏。再来看看客户端加锁方法:

public class ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public synchronized boolean putIfAbsent(E e) {
        boolean absent = !list.contains(e);
        if(absent)
            list.add(e);
        return absent;
    }
}

好,这里我们用装饰器模式包装了一个ArrayList,使其变成线程安全的类,然后又在putIfAbsent方法上加上了内置锁:synchronized关键字,是不是一定保证了线程安全?答案是No

为什么?仔细看看这个类,list的同步性是靠其内置锁完成,而synchronized的同步性则是靠ListHelper的内置锁完成,这两个锁根本不是同一个锁!所以这种做法肯定不是线程安全的,putIfAbsent()相对于list的其他方法并不是原子的,接下来看看正确的做法:

public class ImprovedList<T> implements List<T> {
    private final List<T> list;
    
    public ImprovedList(List<T> list) { this.list = list; }

    public synchronized boolean putIfAbsent(T t) {
        boolean contains = !list.contains(t);
        if(contains)
            list.add(t);
        return contains;
    }
}

这里通过组合实现了线程安全,list的唯一引用被封装在ImprovedList内,所用的锁均为ImprovedList的内置锁,通过转发请求到底层的list上,保证了线程安全性。

猜你喜欢

转载自www.cnblogs.com/cedriccheng/p/9021374.html