1. 设计线程安全的类 尽管将所有的状态都存储在公共静态域中,仍然能写出线程安全的程序。
在没有进行全局检查的情况下,封装能够保证类的线程安全性。
设计线程安全类的过程应该包括下面3个基本要素:
确定对象状态是由哪些变量构成的;
确定限制状态变量的不变约束;
制定一个管理并发访问对象状态的策略。
1.1 同步策略(Synchronization Policy) 定义了对象如何协调对其状态的访问,并且不会违反它的不便约束或验证条件。
使用 Java 监视器模式的简单线程安全计数器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import net.jcip.annotations.GuardedBy;import net.jcip.annotations.ThreadSafe;@ThreadSafe public final class Counter { @GuardedBy("this") private long value = 0 ; public synchronized long getValue () { return value; } public synchronized long increment () { if (value == Long.MAX_VALUE) { throw new IllegalStateException ("counter overflow" ); } return ++value; } }
1.2 收集同步需求 对象与变量拥有一个 状态空间 :这个空间即 它们可能处于状态的范围
。
例如上面的:Counter
类
1 2 3 4 5 6 7 8 private long value = 0 ; if (value == Long.MAX_VALUE) { throw new IllegalStateException ("counter overflow" ); } return ++value;
类似地,操作的后验条件会指出某种 状态转换(state transitions) 是非法的
不变约束
与 后验条件
施加在状态及状态转换上的约束,引入了额外的同步与封装的需要。
1.3 状态依赖的操作
类的 不变约束 与方法的 后验条件 约束了对象 合法的状态
和 合法状态转换
对象的方法也可以有 先验条件
。如无法从空队列中移除一个条目。
单线程中,操作如果无法满足 先验条件
必然失败。
多线程中,原本为 假
的先验条件,可能会由于其它线程的活动变为 真
。
在并发程序中,有持续等待, 直到先验条件为真 ,再继续处理的操作。在 Java 中,等待特定条件成立的内置高效机制 wait
和 notify
与内部锁紧密地绑定在一起。
1.4 状态所有权
在很多情况下,所有权
与封裝性
总是在一起出现的。
对象封装它拥有的状态,且拥有它封装的状态。拥有给定状态的所有者决定了锁协议,该协议用于维护变量状态的完整性。
所有权意味着控制权
,不过一旦将引用发布到一个可变对象上,就不再拥有独占的控制权,充其量只可能有共享控制权
。
类通常不会拥有由构造函数或方法传递进来的对象,除非该方法是被明确设计用来转换传递对象的所有权的(比如同步容器的包装工厂方法
)。
2. 实例限制 2.1 实例限制 即使一个对象不是线程安全的,仍然有许多技术可以让它安全地用于多线程程序。
比如,你可以确保它只被单一线程访问
(线程限制),也可以确保所有的访问都正确地被锁保护
。
通过使用实例限制 (instance confinement)
,封装简化了类的线程安全化工作,这通常称为限制
。
将数据封装在对象内部,把对数据的访问限制在对象的方法上,更易确保线程在访问数据时总能获得正确的锁。
使用限制确保线程安全:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import net.jcip.annotations.GuardedBy;import net.jcip.annotations.ThreadSafe;import java.util.HashSet;import java.util.Set;@ThreadSafe public class PersonSet { @GuardedBy("this") private final Set<Person> mySet = new HashSet <>(); public synchronized void addPerson (Person p) { mySet.add(p); } public synchronized boolean containsPerson (Person p) { return mySet.contains(p); } interface Person { } }
2.2 Java 监视器模式(Java monitor pattern)
线程限制原则
的直接推论之一是Java 监视器模式
。
遵循Java监视器模式
的对象封裝了所有的可变状态,并由对象自己的内部锁保护。
Java的内置锁
有时也被叫做监视器锁
(monitor locks) 或监视器
(monitors)
Counter
演示了这个模式的典型案例:
Counter 封裝了一个状态变量value,所有对该变最的访问都要通过 Counter 的方法,这些方法都是同步的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import net.jcip.annotations.GuardedBy;import net.jcip.annotations.ThreadSafe;@ThreadSafe public final class Counter { @GuardedBy("this") private long value = 0 ; public synchronized long getValue () { return value; } public synchronized long increment () { if (value == Long.MAX_VALUE) { throw new IllegalStateException ("counter overflow" ); } return ++value; } }
私有锁保护状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import apple.laf.JRSUIConstants;import net.jcip.annotations.GuardedBy;public class PrivateLock { private final Object myLock = new Object (); @GuardedBy("myLock") JRSUIConstants.Widget widget; void someMethod () { synchronized (myLock) { } } }
使用私有锁对象,而不是对象的内部锁(或任何其他可公共访问的锁),有很多好处。
私有的锁对象可以封装锁,这样客户代码无法得到它。
可公共访问的锁允许客户代码涉足它的同步策略 —- 正确地或不正确地。
客户不正确地得到另一个对象的锁,会引起活跃度方面的问题。
另外要验证程序是正确地使用着一个可公共访问的锁,需要检查完整的程序,而不是一个单独的类。
2.3 机动车追踪器(tracking fleet vehicle) 每一辆机动车都有一个 string 标识,并有一个与之对应的位置(x,y)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 import net.jcip.annotations.ThreadSafe;import java.awt.Point;import java.util.Collections;import java.util.HashMap;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;@ThreadSafe public class DelegatingVehicleTracker { private 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 ("车辆名称无效: " + id); } } public void vehicleMoved (VehicleMovedEvent evt) { Point local = evt.point; setLocation(evt.vehicleId, local.x, local.y); } public Map<String, Point> getLocationsAsStatic () { return Collections.unmodifiableMap( new HashMap <String, Point>(locations)); } public void renderVehicle (String vehicleId, Point local) { System.out.println("机动车 " + vehicleId + " 位置:" + local); } static class VehicleMovedEvent { Point point; String vehicleId; public VehicleMovedEvent (Point point, String vehicleId) { this .point = point; this .vehicleId = vehicleId; } } }
3. 委托线程安全 3.1 基于监视器的机动车追踪器实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import net.jcip.annotations.GuardedBy;import net.jcip.annotations.ThreadSafe;import java.util.Collections;import java.util.HashMap;import java.util.Map;@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 <>(m.size()); for (String id : m.keySet()) { result.put(id, new MutablePoint (m.get(id))); } return Collections.unmodifiableMap(result); } }
3.2 将现场安全委托到 ConcurrentHashMap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 import net.jcip.annotations.ThreadSafe;import java.awt.Point;import java.util.Collections;import java.util.HashMap;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;@ThreadSafe public class DelegatingVehicleTracker { private 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 ("车辆名称无效: " + id); } } public void vehicleMoved (VehicleMovedEvent evt) { Point local = evt.point; setLocation(evt.vehicleId, local.x, local.y); } public Map<String, Point> getLocationsAsStatic () { return Collections.unmodifiableMap( new HashMap <String, Point>(locations)); } public void renderVehicle (String vehicleId, Point local) { System.out.println("机动车 " + vehicleId + " 位置:" + local); } static class VehicleMovedEvent { Point point; String vehicleId; public VehicleMovedEvent (Point point, String vehicleId) { this .point = point; this .vehicleId = vehicleId; } } }
3.3 非状态依赖变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.awt.event.KeyListener;import java.awt.event.MouseListener;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class VisualComponent { private final List<KeyListener> keyListeners = new CopyOnWriteArrayList <>(); private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList <>(); 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); } }
VisualComponent
使用 CopyOnWriteArrayList
存储每个监听器清单。
在 VisualComponent 中,不但每个 List 是线程安全的,而且不存在哪个不变约束会增加一个状态与另一个状态间的耦合,所以 VisualComponent 可以将它的线程安全贵任委托到 MouseListener 和 KeyListener 对象上。
3.4 不完整地保护不变约束 NumberRange 不是线程安全的;它没有保护好用于约東 lower 和 upper 的不变约束。
setLower 和 setupper 都是检查再运行
的操作,但是它们没有适当地加锁以保证其原子性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import java.util.concurrent.atomic.AtomicInteger;public class NumberRange { private final AtomicInteger lower = new AtomicInteger (0 ); private final AtomicInteger upper = new AtomicInteger (0 ); public void setLower (int i) { if (i > upper.get()) { throw new IllegalArgumentException ("can't set lower to " + i + " > upper" ); } lower.set(i); } public void setUpper (int i) { 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()); } }
如果类中还存在复合操作,如 setLower 和 setupper,类必须提供它自身有的锁以保证复合操作都是原子的。
除非所有的操作可以委托给一个状态变量。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import java.util.concurrent.atomic.AtomicReference;public class NumberRange2 { private final AtomicReference<RangeNum> lowerAndUpper = new AtomicReference <>(new RangeNum (0 , 0 )); public void setLower (int i) { lowerAndUpper.set(new RangeNum (i, lowerAndUpper.get().upper)); } public void setUpper (int i) { lowerAndUpper.set(new RangeNum (lowerAndUpper.get().lower, i)); } public boolean isInRange (int i) { return (i >= lowerAndUpper.get().lower && i <= lowerAndUpper.get().upper); } class RangeNum { public int lower; public int upper; public RangeNum (int lower, int upper) { if (upper < lower) {throw new IllegalArgumentException ("upper must be greater than or equal to lower!" );} this .lower = lower; this .upper = upper; } } }
3.5 发布底层的状态变量 如果一个状态变量是线程安金的,没有任何不变约束限制它的值,并且没有任何状态转换限制它的操作,那么它可以被安全发布。
发布了状态的机动车追踪器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 import net.jcip.annotations.GuardedBy;import net.jcip.annotations.ThreadSafe;import java.util.Collections;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;@ThreadSafe public class PublishingVehicleTracker { private final Map<String, SafePoint> locations; private final Map<String, SafePoint> unmodifiableMap; public PublishingVehicleTracker (Map<String, SafePoint> locations) { this .locations = new ConcurrentHashMap <>(locations); this .unmodifiableMap = Collections.unmodifiableMap(this .locations); } public Map<String, SafePoint> getLocations () { return unmodifiableMap; } public SafePoint getLocation (String id) { return locations.get(id); } public void setLocation (String id, int x, int y) { if (!locations.containsKey(id)) { throw new IllegalArgumentException ("invalid vehicle name: " + id); } locations.get(id).set(x, y); } @ThreadSafe class SafePoint { @GuardedBy("this") private int x, y; public SafePoint (int x, int y) { this .set(x, y); } private SafePoint (int [] a) { this (a[0 ], a[1 ]); } public SafePoint (SafePoint p) { this (p.get()); } public synchronized int [] get() { return new int []{x, y}; } public synchronized void set (int x, int y) { this .x = x; this .y = y; } } }
PublishingVehicleTracker 的线程安全性源自于它所委托的底层 ConcurrentHashMap。
不过这次 Map 的内容是线程安全的可变 SafePoint
,而非不可变的。
getLocation 方法返回底层 Map 的不可变拷贝
,调用者在其上无法添加或移除车辆,却可以通过修改返回的 Map 中 SafePoint 的值,改变一个机动车的位置。
只有 PublishingVehicleTracker 对机动车追踪器的合法值
没有施加任何额外的约束时,它才是线程安全的。
如果需要对机动车的 location
的改变(setLocation()
)进行判断或者执行一些其他的操作,那么 PublishingVehicleTracker 的做法可能就不正确了.
4. 向已有的线程安全类添加功能 4.1 向已有的线程安全类添加功能
4.2 客户端加锁 创建一个助手类,该助手类包含一个作用于线程安全 List 的原子 缺少即加入
的操作。
非线程安全的 “缺少即加入” 实现(不要这样做)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @NotThreadSafe class BadListHelper <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; } }
这里是并不能保证对 list
的操作是线程安全的,即使使用了同步修饰 putIfAbsent()
方法。
虽然对 putIfAbsent()
加了内置锁,但是这仅仅限制同一时间仅有单一线程调用此方法。
我们并不能保证其它线程调用 BadListHelper
的其它方法,对 list
进行操作。即使其它方法也都是用了内置锁。
使用客户端加锁实现的 “缺少即加入”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @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; } } }
线程安全
如果说,为了添加另一个原子操作而去扩展一个类(扩展类加锁)容易出问题,是因为它将加锁的代码分布到了继承体系中的多个类里。
然而客户端加锁其实是更加脆弱的,因为它必须将类 a.class
中的加锁代码置入与a.class
完全无关的类中。
在那些不关注锁策略
的类中使用客户端加锁时,一定要小心。客户端加锁与扩展类有很多共同之处:所得类的行为与基类的实现之间都存在耦合。正如同扩展会破坏实现的封装性一样,客户端加锁会破坏同步策略的封装性。
4.3 组合(composition) 向己有的类中添加一个原子操作,还有更好的选择:组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import net.jcip.annotations.ThreadSafe;import java.util.Collection;import java.util.Iterator;import java.util.List;import java.util.ListIterator;@ThreadSafe 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 x) { boolean contains = list.contains(x); if (contains) { list.add(x); } return !contains; } @Override public synchronized void clear () {list.clear();} @Override public synchronized void add (int index, T element) {list.add(index, element);} @Override public synchronized boolean add (T e) {return list.add(e);} @Override public synchronized boolean remove (Object o) {return list.remove(o);} @Override public synchronized boolean addAll (Collection<? extends T> c) {return list.addAll(c);} @Override public synchronized boolean addAll (int index, Collection<? extends T> c) {return list.addAll(index, c);} @Override public synchronized boolean removeAll (Collection<?> c) {return list.removeAll(c);} @Override public synchronized boolean retainAll (Collection<?> c) {return list.retainAll(c);} @Override public synchronized T set (int index, T element) {return list.set(index, element);} @Override public synchronized T remove (int index) {return list.remove(index);} @Override public T get (int index) {return list.get(index);} @Override public int size () {return list.size();} @Override public boolean isEmpty () {return list.isEmpty();} @Override public boolean contains (Object o) {return list.contains(o);} @Override public Iterator<T> iterator () {return list.iterator();} @Override public Object[] toArray() {return list.toArray();} @Override public <T> T[] toArray(T[] a) {return list.toArray(a);} @Override public boolean containsAll (Collection<?> c) {return list.containsAll(c);} @Override public boolean equals (Object o) {return list.equals(o);} @Override public int hashCode () {return list.hashCode();} @Override public int indexOf (Object o) {return list.indexOf(o);} @Override public int lastIndexOf (Object o) {return list.lastIndexOf(o);} @Override public ListIterator<T> listIterator () {return list.listIterator();} @Override public ListIterator<T> listIterator (int index) {return list.listIterator(index);} @Override public List<T> subList (int fromIndex, int toIndex) {return list.subList(fromIndex, toIndex);} }
ImprovedList 通过将操作委托给底层的 List 实例,实现了 List 的操作,同时还添加了一个原子的putIfAbsent()
方法。
就像 Collections.synchronizedList
和其他容器封装器那样,ImprovedList 假设一旦有一个 list 传给它的构造函数后,客户将不再直接使用这个 list,而仅仅通过 ImprovedList 访问它。
通过使用内部锁,ImproveaList 引入了一个新的锁层
。
它并不关心底层的 List 是否线程安全,即使 List 不是线程安全的,或者会改变 ImproveaList 的锁实现,Improvedtist 都有自己兼容的锁可以提供线程安全性。
虽然额外的一层同步可能会带来一些微弱的性能损失,但是相比于去尝试模拟另一个对象的锁策路而言,ImprovedList并不那么脆弱。
我们己经使用 Java 监视器模式有效地封裝了一个己有的 List,而且只要我们的类持有底层 List 的唯一外部引用
,那么就能保证提供线程安全性。
5. 同步策略的文档化 1.为类的用户编写类线程安全性担保的文档;为类的维护者编写类的同步文档。
2.每次使用synchronized,volatile或者任何线程安全类,都表现了一种同步策略,这个策略是你程序设计的一个元素,因此应该将它文档化。