【Java 并发】对象的组合
发布日期:2021-06-28 20:46:47 浏览次数:2 分类:技术文章

本文共 3624 字,大约阅读时间需要 12 分钟。

【Java 并发】对象的组合

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

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

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

不变性条件,我的理解为约束或者规范,用来判断某个状态是否有效。这个不变性条件可以是对象本身就有,比如一个整型变量就有自己的空间范围,还可以是由开发人员根据需求定的,比如某个int变量不能为负数。还有其他不变性条件:
先验条件,例如,不能从空队列中移除一个元素,在删除元素前,需要判断队列是否处于“非空的”状态。也就是说操作依赖于状态。
后验条件,判断状态迁移,转换是否有效,比如,value++,当前状态是1,则下一个状态只能是2,。也就是下一个状态依赖于当前状态。
此外,如果不变性状态包含多个状态,那么就必须要做到保证原子性和封装性。

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

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

二,构建线程安全类

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

public class PersonSet{
@GuardedBy("this") private final Set
mySet = new HashSet
();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
keyListeners = new CopyOnWriteArrayList
(); private final List
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); }}

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并发编程实践》的笔记,不喜勿喷!

转载地址:https://blog.csdn.net/yangjjuan/article/details/78547315 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:【Java 并发】浅析同步容器类与并发容器类
下一篇:【Java 设计模式】接口型模式--Facade(外观)模式

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年04月02日 19时41分31秒