Java并发编程实战 第一部分 基础知识(三)对象的组合
对象的组合
不希望每一次内存访问都进行分析以确保线程是线程安全的,而是希望将一些现有的线程安全组件,组合为更大规模的组件或程序。
设计线程安全的类
在设计线程安全类的过程中,需要包含以下三个基本要素:
- 找出构成对象状态的所有变量。
- 找出约束状态变量的不变性条件。
- 建立对象状态的并发访问管理策略。
要分析对象的状态,首先从对象的域开始。如果对象中所有的域都是基本类型的变量,那么这些域将构成对象的全部状态。
如下代码,使用Java监视器模式的线程安全计数器
public class Counter {
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;
}
}
在该代码中,Counter中只有一个域value,因此这个value就是Counter的全部状态。
要确保类的线程安全性,就需要确保它的不变性条件不会再并发访问的情况下被破坏,这就需要对其状态进行推断。对象与变量都有一个状态空间,即所有可能的取值。状态空间越小,越容易判断线程的状态。final类型的域使用得越多,就越能简化对象可能状态得分析过程。
为了防止多线程在并发访问同一个对象时产生得相互干扰,这些对象应该要么是线程安全的对象,要么是事实不可变的对象,或者由锁来保护的对象。
实例封闭
如果某对象不是线程安全的,那么可以通过多种技术使其在多线程程序中安全地使用。你可以确保该对象只能由单个线程访问(线程封闭),或通过一个锁来保护对该对象地所有访问。
将数据封装在对象内部,可以将数据地访问限制在对象地方法上,从而更容易确保线程在访问数据时总能持有正确地锁。
被封闭对象一定不能超出它们既定地作用域。对象可以封闭在类地一个实例(作为类的一个私有成员)中,或者封闭在某个作用域内(作为一个局部变量),再或者封闭在线程内(在某个线程中将对象从一个方法传递到另一个方法,而不是在多个线程之间共享该对象)。
如下代码,PersonSet的状态由HashSet来管理的,而HashSet是非线程安全的。由于myset是私有的且不会逸出,因此HashSet被封闭在PersonSet中。唯一能访问的mySet的代码路径是addPerson域containsPerson,在执行它们时都要获取PersonSet的锁。PersonSet的状态完全由它的内置锁保护,因此PersonSet是一个线程安全的类。
import java.util.HashSet;
import java.util.Set;
/**
* 通过线程封闭机制来确保线程安全
*/
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中获得的Person对象时,还需要额外的同步。
* 要想安全的使用Person,方法就是让Person成为一个线程安全的类。
* 也可以使用锁来保护Person对象,确保访问之前已获得正确的锁
*/
class Person{
}
实例封闭是构成线程安全类的一个最简单方式,它还使得在锁策略的选择上拥有了更多的灵活性。
封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无须检查整个程序。
通过一个私有锁来保护状态,如下代码
public class PrivateLock {
private final Object myLock = new Object();
//@GuardedBy("muLock") Method method;
void someMethod(){
synchronized (myLock){
//修改Method的状态
}
}
}
车辆追踪(监视器模式)
实战详情查看 https://www.cnblogs.com/blueSkyline/p/9096725.html
还没有评论,来说两句吧...