java并发编程实践学习(2)--对象的组合
- 先验条件(Precondition):某些方法包含基于状态的先验条件。例如,不能从空队列中移除一个元素,在删除元素前队列必须处于非空状态。基于状态的先验条件的操作成为依赖状态操作。
- 在单线程中,如果某操作无法满足先验条件,就只能失败,但在并发程序中先验条件可能会由于其他线程执行的操作而变成真。
- java中等待某个条件为真的各种内置机制(包括等待和通知机制)都与内置加锁紧密关联。
- 所有权和封装性总是相关联的:对象封装它拥有的所有权,对象对它的封装的状态拥有所有权。
- 发布了某个可变对象的引用,那就不再拥有独占的控制权。
- 容器类通常表现出一种“所有权分离”的形式。
4.1设计线程安全的类
在设计线程安全类的过程中,需要包含以下三个基本要素:
- 找出构成对象状态的所有变量
- 找出约束状态变量的不可变性条件
- 建立对象状态的并发访问管理策略
4.3委托给线程安全的类
可以将共享资源委托给一个线程安全的类。比如ConcurrentHashMap,copyOnWriteArrayList.
如果一个类时由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效状态转换,那么可以将线程安全性委托给底层状态变量。
下面是一个监控车辆位置的实例。其中Point是线程安全不可变的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /** * 不可变 */ @Immutable class Point{ public final int x,y; Point( int x, int y ) { this .x = x; this .y = y; } } /** * 委托给线程安全类的车辆追踪器 */ @ThreadSafe class DelegatingVehicleTracker{ private final ConcurrentHashMap<String,Point> locations; private final Map<String,Point> unmodifiableMap; public DelegatingVehicleTracker(Map<String,Point> points) { this .locations = new ConcurrentHashMap<>(points); this .unmodifiableMap = Collections.unmodifiableMap(locations); } public Map<String,Point> 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); } } } |
如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。
同样是车辆追踪,我想要获取位置,还可以修改位置,安全性问题可以交给底层SafePoint:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /** * 线程安全且可变的Point类 */ @ThreadSafe class SafePoint{ @GuardedBy ( "this" ) private int x,y; private SafePoint( int [] a){ this (a[ 0 ],a[ 1 ]); } public SafePoint(SafePoint p){ this (p.get()); } public SafePoint( int x, int y){ this .x = x; this .y = y; } public synchronized int [] get(){ return new int [] {x,y}; } public synchronized void set( int x, int y){ this .x =x; this .y = y; } } /** * 安全发布底层状态的车辆追踪器 */ @ThreadSafe class PublishingVehicleTracker{ private final Map<String,SafePoint> locations; private final Map<String,SafePoint> unmodifiableMap; PublishingVehicleTracker(Map<String, SafePoint> locations, Map<String, SafePoint> unmodifiableMap) { this .locations = locations; this .unmodifiableMap = unmodifiableMap; } public Map<String,SafePoint> getLocations(){ return unmodifiableMap; } public SafePoint getLocation(String id){ return locations.get(id); } public void setLocation(String id, int x, int y){ if (!locations.containsKey(id)) throw new IllegalArgumentException( "invalid vehicle name:" +id); locations.get(id).set(x,y); } } |
4.5将同步策略文档化
在文档中说明客户代码需要了解的线程安全性保证,以及代码维护人员需要了解的同步策略。
synchronized,volatile或者任何一个线程安全类都对应于某种同步策略,用于在并发访问时确保数据的完整性。一定要在忘记之前记录下来。
可以使用@GuardedBy("this")或者别的来注释锁。
关注我的公众号

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了