您的位置:首页 > 编程语言 > Java开发

【Java 并发】对象的组合

2017-11-16 15:26 197 查看

【Java 并发】对象的组合

一,在设计线程安全类时,需要关注以下三个基本要素:

1,找出构成对象状态的所有变量。

这些变量指的是对象的域,如果对象中的所有域都是基本类型的变量,那么这些域将构成对象的全部状态,比如Counter中的value,或者二维点的状态就是它的坐标值(x,y)。另外,如果对象的域中引用了其他对象,那么该对象的状态将包含被引用对象的域。例如,LinkedList的状态就包含链表中所有节点对象的状态。

2,找出约束状态变量的不变性条件

不变性条件,我的理解为约束或者规范,用来判断某个状态是否有效。这个不变性条件可以是对象本身就有,比如一个整型变量就有自己的空间范围,还可以是由开发人员根据需求定的,比如某个int变量不能为负数。还有其他不变性条件:

先验条件,例如,不能从空队列中移除一个元素,在删除元素前,需要判断队列是否处于“非空的”状态。也就是说操作依赖于状态。

后验条件,判断状态迁移,转换是否有效,比如,value++,当前状态是1,则下一个状态只能是2,。也就是下一个状态依赖于当前状态。

此外,如果不变性状态包含多个状态,那么就必须要做到保证原子性和封装性。

3,建立对象状态的并发访问策略

有相应的机制构建线程安全类

二,构建线程安全类

1,实例封闭机制

实例封闭机制是构造线程安全类的一种最简单方式。当一个对象被封装到另一个对象中时,访问被封装的对象的方式就很清晰。这比可以被整个程序访问更易于实现线程安全。说白了就是通过封闭,控制和限制访问对象的方式,并且这更加容易使用锁机制。示例代码

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


PersonSet的状态都由HashSet管理,而HashSet是非线程安全的,将HashSet封闭在PersonSet,这样可以访问到mySet的方式只能通过addPerson和containsPerson,

在执行这两个方法时需要获得PersonSet的内置锁,所以在假设Person也是线程安全类的情况下,PersonSet就是线程安全类。

在封闭一个对象一定不能超出其设定的作用域,对象可以封闭为类的一个实例(私有成员),封闭在某个作用域(局部变量)和封闭在线程中。在使用时要注意逸出,比如前一篇博文中的在构造函数中声明匿名内部类或者启动线程。

2,线程安全性的委托

我们首先来考虑如果一个类只有一个线程安全的变量,那么很显然,这个类必然是线程安全的。

public class CountingFactorizer extends GenericServlet implements Servlet {
private final AtomicLong count = new AtomicLong(0);

public long getCount() { return count.get(); }

public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
count.incrementAndGet();
encodeIntoResponse(resp, factors);
}
}


在无状态的类中添加一个AtomicLong类型的域,并且得到的组合对象任然是线程安全的。

考虑多个线程安全变量

如果这个类的所有变量都是独立的:

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


keyListeners和mouseListeners之间不存在任何关系,二者相互独立。CopyOnWriteArrayList也是一个线程安全链表,每个链表都是线程安全的,且各个状态之间不存在复合操作。VisualComponent 可以把线程安全委托给这两个变量。

如果这个类的所有变量存在依赖或者说关系时:

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


NumberRange 的两个状态变量都是AtomicInteger,看起来好像线程安全,但是两个变量存在着一个约束条件:lower <= upper。并且程序是“先检查后执行”,假设原来的范围是(0,10),一个线程调用setLower(5),同时另一个线程调用setUpper(4),由于缺少同步机制,两个线程调用都成功,则结果修改为(5.4)。这是个无效状态,虽然AtomicInteger线程安全,但是两个变量不相互独立,因此NumberRange不能把线程安全委托给这两个变量。

如果一个类是由多个独立且线程安全的状态变量组成,并且所有的操作都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。

以上都是阅读《Java编程思想》-并发和《Java并发编程实践》的笔记,不喜勿喷!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线程安全 java 并发