Java并发学习之对象的组合(设计线程安全的类)
一.本章概述
本章将介绍一些组合模式,这些模式能够使一个类更容易成为线程安全的类,并且在维护这些类时不会无意的破坏类的安全性保证。
学习目标:
1.设计线程安全的类
2.实例封闭
3.线程安全性的委托
4.在现有的线程安全的类中添加功能
二.具体学习
1.设计线程安全的类
在设计线程安全的类的过程中,需要包含下面三个要素:
1)找出构成对象状态的所有变量
要分析对象的状态,首先从对象的域开始:
a.如果对象中所有的域都是基本类型的变量,那么这些域将构成对象的全部状态
b.如果在对象的域中引用了其他的对象,那么该对象的状态将包含它引用的对象的域。
2)找出约束状态变量的不可变条件
a.要确保类的线程安全性,就要确保它的不可变性条件不会再并发访问的情况下被破坏。
b.对象和变量都有一个状态空间(所有可能的取值),状态空间越小,就越容易判断线程的状态,故final类型的域使用的越多,就越能简化对象可能状态的分析过程。
c.如果不了解对象的不可变条件与后验条件,那么就不能确保线程安全性,要满足再状态变量的有效值或状态转换上的各种约束条件,就需要借助原子性和封装性。
3)建立对象状态的并发访问管理策略
同步策略:它定义了如何在不违背对象不变条件或后验条件的条件下对其状态的访问操作进行协同,同步策略规定了如何将不可变性,线程封闭与加锁机制等结合起来以维护线程的安全性,并且还规定了那些变量由那些锁保护。
总结:如果想要设计出一个线程安全的类,首先我们就要确保类中所有变量的封装性,然后确保类中方法对变量操作的安全性,总之就是确保在多个线程同时访问时的正确性。
2.实例封闭
如果某对象不是线程安全的,那么我们可以通过多种技术使其在多线程程序中安全的使用
我们可以确保该对象只能由单个线程访问,或者通过一个锁来保护该对象的所有访问。
封装简化了线程安全类的实现过程,它提供了一种实例封闭机制,当一个对象被封装到另一个对象中,能够访问被封装对象的所有代码路径都是已知的,并通过将封装机制与合适的加锁策略结合在一起,可以确保以线程安全的方式来使用非线程安全的类。
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无需检查整个程序。
例子:通过线程封闭机制来确保线程安全
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);
}
}
从上面的代码我们可以看到,其中的Person的线程安全性未知,在PersonSet中使用了它的内置锁来保护它在mySet中的状态。
1)java监视器模式
什么是java监视器模式?
遵循java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁保护。
下面我们通过一个实例来学习它:
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MonitorVehicleTracker {
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 setLocations(String id,