保证线程安全的设计模式

2016-07-30 23:19:59   最后更新: 2016-07-30 23:19:59   访问数量:483




前面的日志中,介绍了基本的线程安全知识,本篇日志中将介绍用来保证线程安全的设计模式,这是构建大规模工程的基础

 

在设计线程安全类的过程中,需要包含以下三个基本要素:

  1. 找出构成对象状态的所有变量
  2. 找出月梳妆台变量的不变形条件
  3. 建立对象状态的并发访问管理策略

 

类的提供者为了保证开发人员对类的分析和维护,必须将包括如何在不违背对象不变性条件或后延条件的情况下访问对象状态在内的同步策略写入正式文档

 

如果对象不是线程安全的,你可以通过多种技术来确保对象只能由单个线程访问,或者通过锁的方式保护对象的所有访问

实例封闭机制是一种将对象实例封装到另一个对象中的线程安全设计模式,通过实例封闭与核实的加锁策略结合,可以确保以线程安全的方式调用非线程安全的对象

public class PersonSet { 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); } }

 

 

java 中的 Set 容器并不是线程安全的,上面的例子中,我们通过将我们的 set 容器对象设为 private、final 来将他封闭在类私有成员中,外部只能通过两个 public 方法来操作他,而这两个方法通过加锁的方式实现了调用的线程安全性

 

实例封闭是构建线程安全类最简单的方式,同时,他还使得锁策略拥有了更多灵活性

你可以将实例封闭到类成员中,或者某个作用域内(比如作为方法局部变量),或者封闭在某个线程内,只要保证他没有被逸出,那么他就是线程安全的

java 的很多基本容器,如 ArrayList、HashMap 等都是非线程安全的,但是类库同时提供了包装器工厂方法,如 Collections.synchronizedList 等类似方法,实现了“装饰器”模式,对这些非线程安全的类库对象进行了封闭,保证调用的线程安全性

 

监视器模式要求对象将他所有可变的状态都封装起来,并由对象自己的内置锁来保护

public class PrivateLock { private final Object myLock = new Object(); private Widget widget; public void publicMethod() { synchronized(myLock) { // 访问或修改 widget 的状态 } } }

 

 

上面的例子中,我们在类内部使用了 private 的锁对象,这样做的好处是在类外将无法使用它,如果使用公有锁,那么很可能因为其他方法占用了该锁,而让本应进入锁区域的线程阻塞

 

很多情况下,我们需要设计一个委托类,将很多个对象托管在其中,来统一管理,此时需要注意的是,多个线程安全类对象及状态的操作被委托到委托类以后,委托类可能并不是线程安全的,这种情况下就需要更加细致的思考和设计了

public class NumberRange { private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0); private final Object upperLock = new Object(); private final Object lowerLock = new Object(); public void setLower(int i) { synchronized(upperLock) { if (i > upper.get()) { throw new IllegalArgumentException("out of the range"); } lower.set(i); } } public void setUpper(int i) { synchronized(lowerLock) { if (i < lower.get()) { throw new IllegalArgumentException("out of the range"); } upper.set(i); } } public boolean isRange(int i) { return (i >= lower.get() && i <= upper.get()); } }

 

 

虽然作为原子性的 lower 和 upper 两个成员单独使用时可以保证线程安全的,但是上面的类中 setLower 和 setUpper 方法是非线程安全的,因为在他们判断的同时,可能有另一个线程调用另一个方法致使判断的结果出现了错误

在上面的例子中,我们发现,如果某个类含有复合操作,那么,仅靠线程委托并不足以实现线程安全性,这种情况下,委托类需要提供自己的加锁机制来保证这些复合操作的原子性

 






技术帖      龙潭书斋      sync      线程      thread      java      synchronized      java并发编程实战      同步     


京ICP备15018585号