并发编程学习(3)-----对象的组合
发布日期:2021-06-29 04:12:26 浏览次数:2 分类:技术文章

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

思维导图:

引言:

    在前两篇文章中,我们介绍了如何判断一个类是否是线程安全的,也介绍了如何判断一个变量是否可以被其他线程使用,即可见性。以上两篇文章是构造线程安全类的基础。在此基础之上,我们将这在这篇文章中,初步地介绍如何从无到有或者利用已有的线程安全类的资源构造我们自己的线程安全的类。按照惯例,文章大体分成两部分:

  1. 理论部分:我称之为同步要素分析,即分析当前组合类所拥有的的对象(状态),以及他们之间互相约束的条件。
  2. 使用部分:我称之为同步管理策略,即在分析了当前的状态和约束条件后,选择不同的方式构建所需要的线程安全类。

一.同步要素分析

    构造一个线程安全的类,我们需要对类中的两类要素进行分析,状态和状态的约束条件

1.1 构成对象状态的变量

    对于一个类来说,域变量是决定其是否线程安全的要素之一。但是,域变量和域变量所包含的域变量(递归成引用树)却不全都是当前类的状态。那么,什么样的域变量才属于类的状态呢?

    若当前类的所有域变量都是基本类型,那么所有的基本类型变量都是类的状态。

    若当前类含有对象引用,而此引用所含有的部分域变量的控制权不在当前类中(即未发布到当前类)时,那么,此引用对象的这部分域变量不属于当前类的状态,如果当前类可以控制引用对象的域变量则属于当前类的状态。

1.2 约束变量的条件

    要构造一个线程安全的类,必不可少的考虑条件就是状态之间的约束关系。一般来说,我们可以从以下几点分析状态间的约束关系:

  • 不变性条件:即状态和状态之间存在逻辑联系,例如x是个自然数,y[] 则是x因式分解后的数构成的数组,那么x和y[]之间是                        存在不变性条件的。
  • 后验性条件:即状态的改变需要由以前的条件决定,典型的例子是x = x + 1,新x是由老x的值决定的。
  • 状态性条件:即状态的改变与否由另一个状态的状态决定,例如进行非空判断后再执行部分代码。

二.同步管理策略

    在分析了上述的两大要素后,我们根据不同的情况采用不同的手段来构建一个线程安全的类。

2.1 监视器模式

    当我们想从无到有或者将手头上非线程安全的类转化为一个线程安全的类时,监视器模式是非常适合的。其核心就是将我们需要的对象封闭到一个用于修饰的类中(即只有此类可以访问这些对象),然后所有访问这些对象的手段都必须经过此修饰类进行同步处理,这种方法称之为实例封闭

    例如在下列代码中,我们希望实现一个类,用于记录所有车辆及其位置信息。所以,此类会被两种线程所访问,一是获取车辆信息的线程,可能会 用于生成车辆的分布信息。另一种就是需要更新车辆位置的线程。其核心数据结构是一个非线程安全的Map,但是经过外部类的访问控制后,就可以变为线程安全的类。

    其中 MutablePoint 用于储存车辆的位置信息,这是一个不安全的类。Map也不安全,但使用实例封闭就可以构成一个线程安全的类。

@ThreadSafe public class MonitorVehicleTracker {    @GuardedBy("this") private final Map
locations; public MonitorVehicleTracker(Map
locations) { this.locations = deepCopy(locations); } public synchronized Map
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 setLocation(String id, int x, int y) { MutablePoint loc = locations.get(id); if (loc == null) { throw new IllegalArgumentException("No such ID: " + id); } loc.x = x; loc.y = y; } private static Map
deepCopy(Map
m) { //获取当前车辆位置的快照 Map
result = new HashMap
(); for (String id : m.keySet()){ result.put(id, new MutablePoint(m.get(id))); } return Collections.unmodifiableMap(result); }}@NotThreadSafeclass MutablePoint { public int x, y; public MutablePoint() { x = 0; y = 0; } public MutablePoint(MutablePoint p) { this.x = p.x; this.y = p.y; }}

2.2 安全委托

    将类的线程安全性委托给别的线程安全类称之为安全委托。新的类需要一个线程安全类或者在此线程安全类上进行某种改动,也可能需要多个线程安全的类才可以实现其功能。我们需要根据不同的情况进行不同的处理。

2.2.1 委托给一个线程安全的类

    当我们所需要的功能完全可以由一个已存在的线程安全的类提供的话,那只需要进行简单的组合即可。如下代码所示:依旧是管理车辆的位置,线程安全性完全由ConcurrentMap保证,当前类不需要做额外的显示同步

@ThreadSafepublic class DelegatingVehicleTracker {    private final ConcurrentMap
locations; private final Map
unmodifiableMap; public DelegatingVehicleTracker(Map
points) { locations = new ConcurrentHashMap
(points); unmodifiableMap = Collections.unmodifiableMap(locations); } public Map
getLocations() { return unmodifiableMap; } public Point getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if (locations.replace(id, new Point(x, y)) == null) throw new IllegalArgumentException("invalid vehicle name: " + id); }}

2.2.2 扩展线程安全的类

    在大多数情况先下,我们所需要的功能都是在一个已存在的线程安全类基础之上进行增添。我们可以选择进行继承重写或者修改其源代码,但是这种手段容易破坏原有的线程安全类的不变性条件,所以不推荐使用。

    相应的,我们可以构建一个新的客户端,由客户端实现新的功能并控制同步。例如:我们当前需要在一个List中实现若没有则添加的功能,请注意,此时加锁需要加在线程安全的List上,而不是新的客户端类上,否则这个若没有则添加的功能对list的其他功能来说并不是原子的和可见的。如下列代码所示:

@NotThreadSafeclass BadListHelper 
{ public List
list = Collections.synchronizedList(new ArrayList
()); //注意!!!这样加锁是错误的,因为这个锁是BadListHelper的内置锁,而其他线程所获得的是list的锁。 public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contains(x); if (absent){ list.add(x); } return absent; }}@ThreadSafeclass GoodListHelper
{ public List
list = Collections.synchronizedList(new ArrayList
()); public boolean putIfAbsent(E x) { synchronized (list) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } }}

2.2.3 委托给两个独立的线程安全类

    当我们所需要的功能必须由两个状态实现,而且这两个状态都可以有线程安全类提供且互相独立的话,也就是说这两个状态变量之间不存在不变性条件的话,那么可以直接的将线程安全性委托给这两个独立的状态变量,而不需要做特殊的同步性处理:例如,我们需要检测鼠标和键盘的输入事件,这两者是完全独立的,所以,我们完全可以用两个线程安全的list储存这两种监听事件而不导致冲突的发生。代码如下所示:

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

2.2.4 多个状态的组合使用

    当新的类需要多个线程安全类作为域变量且这些域变量之间存在约束的话,就不能简单的把线程安全性委托给线程安全类,而是需要我们自己做同步处理。

    下列代码不是线程安全的,他由两个线程安全的域变量构成,这两个状态的不变性条件是一个比另一个大。直接组合他们并不能使新的类获得线程安全性。比如,当前为(0,10)有两个线程同时调用了setLower(5)和setUpper(4),在特殊的执行时序下,他们都将通过检查,结果就成了(5,4),破坏了类的不变性条件。如下代码所示:

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

    所以,多个状态变量时,需要考虑状态变量的线程安全性以及状态间的约束条件的安全性,并进行相应的同步处理。

    最后,一个类是否是线程安全的应该在文档中进行维护,更进一步,可以维护更详细的同步策略。如果遇见一个没有声明是否是线程安全的类,那么,就不要假设他是线程安全的。

 

 

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

上一篇:并发编程学习(4)-----基础构建模块
下一篇:并发编程学习(2)-----对象的共享

发表评论

最新留言

不错!
[***.144.177.141]2024年04月25日 23时28分42秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

paip.输入法编程---智能动态上屏码儿长调整--.txt 2019-04-29
Atitit sumdoc t0 final index D:\BaiduNetdiskDownload\sumdoc t0 final\sumdoc t0 wps cld bek D:\Baid 2019-04-29
Atitit sumdoc t0 final index D:\BaiduNetdiskDownload\sumdoc t0 final\sumdoc t0 wps cld bek D:\Baid 2019-04-29
Atitit sumdoc t0 final index 2019-04-29
atitit 编程语言选型知识点体系.docx 编程语言选型时,你需要考虑的几个方面 目录 1. 1.2. 类型系统 5 1 2. 1.5. 语言规范 25 1 3. 1.6. 编程范式 52 2019-04-29
Atitit 编程语言语言规范总结 目录 1. 语言规范 3 2. Types 3 2.1.1. Primitive types 3 2.1.2. Compound types 4 3. State 2019-04-29
Atitit QL查询语言总结 目录 1. QL = Query Language, 是查询语言的简称 1 2. 具体实现 1 2.1. Apcl 流程控制语言 1 2.2. 脚本流程控制 2 2. 2019-04-29
Atitit 开发效率大法 v0 t025.docx Atitit 提升开发效率几大策略 目录 1. 提升效率三原则 3 1.1. 更少的代码量简化 3 1.2. 优化配置减少等待 3 1.3. 2019-04-29
Atitit mybatis的扩展使用sql udf,js java等语言 目录 1.1. 默认,mybatis使用xml,sql等语言来书写业务流程 1 2. 使用ognl调用java函数 1 3 2019-04-29
Atitit if else 选择决策流程ast对比 sql java 表达式类型 binaryExpression hase left and rit expr 目录 1.1. Sql 1 2019-04-29
Atitit 数据库存储引擎 目录 1.1. BLACKHOLE 黑洞引擎 1 1.2. Myisam innodb 1 1.3. Archive 档案类 1 1.4. Fed 连接引擎 2 1. 2019-04-29
Atitit sql注入的防范 目录 1.1. 检查数据类型 1 2. 有限操作DML 1 2.1. 限制执行函数黑名单机制 2 2.2. 限制执行系统sp 2 2.3. 限制数据查询语句类型,只能 2019-04-29
Atitit 自然语言与人工语言的语法构建ast的异同点 目录 1. 语言节点gaishu。。 2 1.1. 节点、函数数量大约200个 2 1.2. 关键词节点 是 有 的 3 1.3. 标识符 2019-04-29
Atitit 效率提升法细则 v3 t028.docx Atitit 提升效率细则 目录 1. 目标 2 1.1. 配置化增加扩展性 尽可能消除编译 方便增加 调整业务逻辑 2 1.2. 统一接口 2019-04-29
Atitit 工程师程序员技术级别对应表与主要特征 P1--p6 说明 类别 职称 对应技术标志 P5 高级工程师 工程师类 一般四五年 P6 资深开发 工程师类 78年经历 P7 P7 2019-04-29
paip.activex控件在WEB中使用流程与工具 2019-04-29
paip.软件及网站项目开发效率低下的思索与改进 2019-04-29
Atitit 可移植性之道attilax著 2019-04-29
paip.截屏功能流程说明 2019-04-29
Atiitt uke兼wag集团2017年度成果报告总结 attilax著 1. 组织机构进一步完善 8大首席部门 1 2. 事业部进一步完善,以及一百多个事业部了 1 3. 企业文化进一步完善 1 2019-04-29