在工作中,我发现很多同学在设计之初都是直接按照单线程的思路来写程序的。
而忽略了本应该重视的并发问题。
关于这个问题,我们今天来聊聊 如何用面向对象思想写好并发程序。
面向对象思想跟并发编程有关系嘛?本来是没有关系的,他们分属两个不同的领域,但是在Java 语言里,这两个领域被无情的融合在一起了。**在 Java 语言里,面向对象思想能够让并发编程变得更简单。 **
那么如何才能用面向对象思想写好并发程序呢?结合我自己的经验来看,我觉得可以从封装共享变量,识别共享变量间的约束条件和指定并发访问策略这三个方面下手。
一、封装共享变量
并发程序,我们关注的一个核心问题,不过是解决多线程同时访问共享变量的问题。编程领域对于共享变量的访问,必须严格控制,好在有了面向对象思想,对于变量的访问路径可以轻松把控。
面型对象里面有一个很重要的特性就是封装,封装的通俗解释就是将属性和细节封装在对象内部。外界对象只能通过目标对象提供的公共方法来间接访问这些内部属性,我们把共享变量作为对象的属性,那对于共享变量的访问路径就是对象的公共方法。
利用面向对象思想写并发程序,其实就这么简单,**将共享变量作为对象属性封装在内部,对所有公共方法制定并发访问策略。 **
就拿很多都会用到的计数器来说,下面的计数器程序共享变量只有一个,就是value ,我们把他作为Counter 类的属性,并且将两个公共方法 get() 和 addOne() 声明为同步方法,这样 Counter 类就成为一个线程安全的类了。
public class Counter {
private long value;
synchronized long get(){
return value;
}
synchronized long addOne(){
return ++value;
}
}
当然很多实际工作中要复杂的多,对于那些不会发生变化的共享变量来说,建议你用fianl 关键字修饰。这样既可以避免并发问题,也能很明了的表明你的设计意图,让后面接手你的兄弟知道,你已经考虑这些共享变量的安全性问题了。
二、识别共享变量间的约束条件
识别共享变量的约束条件非常重要,因为这些约束条件,决定了并发访问策略,例如:库存管理里面有个合理库存的概念,库存量不能太高,也不能太低,他有一个上限和一个下限,我们可以通过下面代码模拟以下:
在类 SafeWM 中,声明了两个成员变量 upper 和 lower,分别代表库存上限和库存下限,这两个变量用了 AtomicLong 这个原子类,原子类是线程安全的,所以这两个成员变量的 set 方法就不需要同步了。
public class SafeWM {
// 库存上限
private final AtomicLong upper =
new AtomicLong(0);
// 库存下限
private final AtomicLong lower =
new AtomicLong(0);
// 设置库存上限
void setUpper(long v){
upper.set(v);
}
// 设置库存下限
void setLower(long v){
lower.set(v);
}
// 省略其他业务代码
}
在没有识别出库存下限要小于库存上限这个约束条件之前,我们制定的并发访问策略是利用原子类,但是这个策略,完全不能保证库存下限要小于库存上限这个约束条件。所以说,在设计阶段,我们一定要识别出所有共享变量之间的约束条件,如果约束条件识别不足,很可能导致制定的并发访问策略南辕北辙。共享变量之间的约束条件,反映在代码里,基本上都会有 if 语句,所以,一定要特别注意竞态条件。
三、制定并发访问策略
制定并发访问策略,是一个非常复杂的事情。应该说整个专栏都是在尝试搞定它。不过从方案上来看,无外乎就是以下“三件事”。
1、避免共享:避免共享的技术主要是利于线程本地存储以及为每个任务分配独立的线程。
2、不变模式:这个在 Java 领域应用的很少,但在其他领域却有着广泛的应用,例如 Actor 模式、CSP 模式以及函数式编程的基础都是不变模式。
3、管程及其他同步工具:Java 领域万能的解决方案是管程,但是对于很多特定场景,使用 Java 并发包提供的读写锁、并发容器等同步工具会更好。
除了这些方案之外,还有一些宏观的原则需要你了解。这些宏观原则,有助于你写出“健壮”的并发程序。这些原则主要有以下三条。
1、优先使用成熟的工具类:Java SDK 并发包里提供了丰富的工具类,基本上能满足你日常的需要,建议你熟悉它们,用好它们,而不是自己再“发明轮子”,毕竟并发工具类不是随随便便就能发明成功的。
2、迫不得已时才使用低级的同步原语:低级的同步原语主要指的是 synchronized、Lock、Semaphore 等,这些虽然感觉简单,但实际上并没那么简单,一定要小心使用。
3、避免过早优化:安全第一,并发程序首先要保证安全,出现性能瓶颈后再优化。在设计期和开发期,很多人经常会情不自禁地预估性能的瓶颈,并对此实施优化,但残酷的现实却是:性能瓶颈不是你想预估就能预估的。
网友评论