java并发编程实践笔记一

目录

不可变对象

JMM中final域的内存语义

实例限制

委托线程安全

向线程安全类添加功能


不可变对象

满足不可变对象的有三点:

1.它的状态(对象关联的成员属性)不能在构造后被修改,除构造方法外没有提供修改状态的代码途径。

2.所有域都是final的。

3.它被正确地创建,即:构造期间没有发生过this引用的逸出。

下面的讲解摘自外网:http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

1.the object is transitively reachable from a final field  表示状态的对象(final字段)可以通过final字段来传递式地获取到。

可以通过跟随零个或多个引用从final字段到达您想要的不可变的数据。如下类你可以bytes字段访问字节数组:

class String {
  private final byte[] bytes;
  public String(byte[] value) {
    byte[] newArray = new byte[value.length];
    System.arraycopy(value, 0, newArray, 0, value.length);
    bytes = newArray;
  }
}

 而如下类,仍然可以通过final的bytes字段来传递式地访问字节数组。

class String {
  		static class BytesHolder {
    		byte[] bytes;
  		}
  		final BytesHolder holder;
}

实际上,您可以根据需要添加任意数量的间接层 - 只要您可以通过跟踪字段中的引用传递式地访问那里,它仍然可以从字段中被传递式地访问到。

2.the object has not changed since the final field was set 自从final字段被设置以来,表示状态的对象(final字段)从没有改变过。

这里有个String类,其字节不再是不可变的。如果两个线程调用setZerothElt方法,则可能上演各种糟糕的线程马戏。

class String {
  private final byte[] bytes;
  public String(byte[] value) {
    byte[] newArray = new byte[value.length];
    System.arraycopy(value, 0, newArray,
        0, value.length);
    bytes = newArray;
  }
  public setZerothElt(byte b) {
    byte[0] = b;
  }
}

 事实证明,如果您希望数据是线程安全的,那么在设置final字段后就不应该更改它,不应该提供修改final字段的代码途径。而且,在设置final字段后还可以更改其状态,不可变对象在语义上只是在构造函数结束后无法更改其final字段。

class String {
  // Absolutely fine.
  private final byte[] bytes;
  public String(byte[] value) {
    bytes = new byte[value.length];
    System.arraycopy(value, 0, 
      bytes, 0, value.length);
  }
}

您可以将构造函数的结尾视为“冻结”操作,对象的final字段的值必须在构造函数的末尾冻结。 

3.A reference to the object containing the final field did not escape the constructor 对包含final字段的对象的引用没有在构造函数里逸出。

逸出构造函数意味着在构造函数中,你存储了一个对正在构造的对象的引用,而另一个线程获取了这个引用,并且正在使用它。例如:

class String {
  // Don't do this.
  static Set<String> allStrings =
  Collections.synchronizedSet(new HashSet());
  private final byte[] bytes;
  public String(byte[] value) {
    allStrings.add(this);
    bytes = new byte[value.length];
    System.arraycopy(value, 0, 
      bytes, 0, value.length);
  }
}

另一个线程使用allStrings中的”this”时,bytes这个final字段可能还没准备好数据(还没达到想要的状态),你可能想到把allStrings.add(this);放到构造方法的结尾处,所以我们又有如下逸出构造方法的例程: 

class String {
// Don't do this, either.
  static String lastConstructed;
  private final byte[] bytes;
  public String(byte[] value) {
    bytes = new byte[value.length];
    System.arraycopy(value, 0, 
      bytes, 0, value.length);
    lastConstructed = this;
  }
}

 当读到lastConstructed=this时,你会一直认为它总会给你最后构造的String实例。设想一下多线程来使用这个lastConstructed的情景,在实际测试中线程有可能会看到未初始化的bytes数据,这是编译器愚弄了你,编译器把lastConstructed = this;这句移动到了bytes = new byte[value.length];之前。

所以,我们要避免不可变对象的引用逸出构造方法。

JMM中final域的内存语义

不可变对象构造方法中为什么不能“逃逸”this?其实这是final域的内存语义要求(JMM的规范),它有一套读写final域的重排序规则,如下:

1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

也就是:在写final域之后构造方法返回之前编译器会插入StoreStore内存屏障指令,在读不可变对象引用之后读final域之前编译器会插入LoadLoad内存屏障指令。

至于什么是内存屏障指令,什么是重排序,话题会很长,这里不展开,具体参见《并发编程的艺术》一书。

实例限制

封装简化了类的安全化工作,封装就是一种限制,因为当一个对象被另一个对象封装时,所有访问被封装对象的代码途径全部在封装对象中可知,而对外部整个系统来说暴露只是访问被封装对象的接口,并没有暴露被封装对象,这样地限制在类实例中,再与适当的锁策略相结合,就可以确保程序以线程安全的方式访问其他非线程安全对象。

总结一句,将数据封装在对象内部,把对数据的访问限制在对象的方法上,更容易确保线程在访问数据时总能获得正确的锁。即,封装更易于做线程安全化工作

可以把对象(数据)限制在类实例(比如私有类成员)、语汇范围(比如本地变量)、线程(比如对象在线程内部从一个方法传递到另一个方法,该对象不被跨线程共享)中。下面的例子示范了限制和内部锁一起确保了一个类的线程安全性:

/**
 * PersonSet
 * <p/>
 * Using confinement to ensure thread safety
 *
 * @author Brian Goetz and Tim Peierls
 */

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

    interface Person {
    }
}

封装或者可以说是包装。平台类库中有许多限制和锁结合的例子,就是Collections.synchronized…这样的方法产生的包装器对象,只要包装器对象有着对被包装对象的唯一引用,那么包装器对象就是线程安全的类。

发布其他对象也可能间接地发布了本应该受限制的被封装(包装)对象,比如你发布了被封装对象的迭代器,或者你发布了一个内部类实例(内部类实例有使用被封装对象的引用),这样被封装对象就逸出了。

委托线程安全

看举例:

基于监视器(内部锁)的实现类:

/**
 * MonitorVehicleTracker
 * <p/>
 * Monitor-based vehicle tracker implementation
 *
 * @author Brian Goetz and Tim Peierls
 */
@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
 * <p/>
 * Mutable Point class similar to java.awt.Point
 *
 * @author Brian Goetz and Tim Peierls
 */
@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为不可变对象Point(它是线程安全的): 

/**
 * Point
 * <p/>
 * Immutable Point class used by DelegatingVehicleTracker
 *	这个类在构造完成后,没有提供修改其状态的接口(代码途径)
 * @author Brian Goetz and Tim Peierls
 */
@Immutable
public class Point {
    public final int x, y;

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

然后我们再把MonitorVehicleTracker的线程安全问题委托给ConcurrentMap和unmodifiableMap去考虑: 

/**
 * DelegatingVehicleTracker
 * <p/>
 * Delegating thread safety to a ConcurrentHashMap
 *
 * @author Brian Goetz and Tim Peierls
 */
@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);
    }

    // Alternate version of getLocations (Listing 4.8)
    public Map<String, Point> getLocationsAsStatic() {
        return Collections.unmodifiableMap(
                new HashMap<String, Point>(locations));
    }
}

 注:如果我们还用MutablePoint去代替Point,那么getLocations和getLocation就发布了一个非线程安全的可变的引用,破坏了封装性,其实就是暴露了对被封装对象的一部分访问,也就是没有封装好,也就是实例限制没做到位。

也可以将线程安全委托到多个状态变量上。例如:

/**
 * VisualComponent
 * <p/>
 * Delegating thread safety to multiple underlying state variables
 *
 * @author Brian Goetz and Tim Peierls
 */
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);
    }
}

注:在这个类中,2个list都是线程安全的类,而且2个状态之间没有不变约束的耦合,所以该类的委托行为是线程安全的,该类是线程安全的。然而如下例:  

/**
 * NumberRange
 * <p/>
 * Number range class that does not sufficiently protect its invariants
 *
 * @author Brian Goetz and Tim Peierls
 */

public class NumberRange {
    // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

    public void setLower(int i) {
        // Warning -- unsafe check-then-act
        if (i > upper.get())
            throw new IllegalArgumentException("can't set lower to " + i + " > upper");
        lower.set(i);
    }

    public void setUpper(int i) {
        // Warning -- unsafe check-then-act
        if (i < lower.get())
            throw new IllegalArgumentException("can't set upper to " + i + " < lower");
        upper.set(i);
    }

    public boolean isInRange(int i) {
        return (i >= lower.get() && i <= upper.get());
    }
}

假设当前值域是(0,5),一个线程setLower(5),另一个线程setUpper(4),在一些偶发的时段里,这两个方法都通过了检查,并且使值域转换成了(5,4)这些无效的状态。所以,虽然AtomicInteger是线程安全的,但组合起来不是安全的,因为lower和upper之间有不变约束的耦合,所以可以通过枷锁来维护状态变量之间的不变约束。

总结一句话,如果一个类由多个彼此独立的线程安全的状态变量组成,并且类的方法不包括任何无效状态的转换,则该类可以线程安全地委托给这些状态变量。

另外,如果一个状态变量是线程安全的,且没有任何的不变约束限制它,那么它可以安全地发布。例如,VisualComponent中的2个keyListeners列表。

向线程安全类添加功能

重用能够降低开发的难度、风险(因为已有的组件已经经过测试)和维护成本。而一个类有时候只支持我们的大部分操作,没法满足我们需要的全部操作,这时候,我们要在不破坏线程安全的前提下,向他添加一个操作。

例如我们向Vector添加一个操作:

/**
 * BetterVector
 * <p/>
 * Extending Vector to have a put-if-absent method
 *
 * @author Brian Goetz and Tim Peierls
 */
@ThreadSafe
public class BetterVector <E> extends Vector<E> {
    // When extending a serializable class, you should redefine serialVersionUID
    static final long serialVersionUID = -3963416950630760754L;

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

有时候我们添加的安全操作,会出现同步幻象,例如:

/**
 * ListHelder
 * <p/>
 * Examples of thread-safe and non-thread-safe implementations of
 * put-if-absent helper methods for List
 *
 * @author Brian Goetz and Tim Peierls
 */

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

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

注:putIfAbsent操作和list的操作使用了不同的锁,所以也没法保证线程安全。然而如下:

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

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

总结,扩展安全方法和客户端加锁(GoodListHelper)他们所得类的行为与基类(Vector和SynchronizedList)的实现之间都存在耦合,实现时一定要小心谨慎。而且,扩展会破坏基类的封装性,客户端加锁会破坏同步策略的封装性(实例限制)。

然而为了更好地添加一个安全操作,我们引入如下的组合模式的实现:

/**
 * ImprovedList
 *
 * Implementing put-if-absent using composition
 *
 * @author Brian Goetz and Tim Peierls
 */
@ThreadSafe
public class ImprovedList<T> implements List<T> {
    private final List<T> list;

    /**
     * PRE: list argument is thread-safe.
     */
    public ImprovedList(List<T> list) { this.list = list; }

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

    // Plain vanilla delegation for List methods.
    // Mutative methods must be synchronized to ensure atomicity of putIfAbsent.
    
    public int size() {
        return list.size();
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    public boolean contains(Object o) {
        return list.contains(o);
    }

    public Iterator<T> iterator() {
        return list.iterator();
    }

    public Object[] toArray() {
        return list.toArray();
    }

    public <T> T[] toArray(T[] a) {
        return list.toArray(a);
    }

    public synchronized boolean add(T e) {
        return list.add(e);
    }

    public synchronized boolean remove(Object o) {
        return list.remove(o);
    }

    public boolean containsAll(Collection<?> c) {
        return list.containsAll(c);
    }

    public synchronized boolean addAll(Collection<? extends T> c) {
        return list.addAll(c);
    }

    public synchronized boolean addAll(int index, Collection<? extends T> c) {
        return list.addAll(index, c);
    }

    public synchronized boolean removeAll(Collection<?> c) {
        return list.removeAll(c);
    }

    public synchronized boolean retainAll(Collection<?> c) {
        return list.retainAll(c);
    }

    public boolean equals(Object o) {
        return list.equals(o);
    }

    public int hashCode() {
        return list.hashCode();
    }

    public T get(int index) {
        return list.get(index);
    }

    public T set(int index, T element) {
        return list.set(index, element);
    }

    public void add(int index, T element) {
        list.add(index, element);
    }

    public T remove(int index) {
        return list.remove(index);
    }

    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }

    public ListIterator<T> listIterator() {
        return list.listIterator();
    }

    public ListIterator<T> listIterator(int index) {
        return list.listIterator(index);
    }

    public List<T> subList(int fromIndex, int toIndex) {
        return list.subList(fromIndex, toIndex);
    }

    public synchronized void clear() { list.clear(); }
}

ImprovedList使用自己的内部锁,引入了一个新的锁层,它并不关心被封装的list是否线程安全,即使额外的一层锁会带来一些微弱的性能损失,但相比于客户端加锁去模拟另一个对象的锁策略而言,ImprovedList并不那么脆弱。只要使用监视器模式封装一个已有的list,并且唯一持有list的引用,那么就能保证线程安全。

转载笔记请注明出处。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值