重用可以节省我们进行开发和测试(测试比我们自己测严谨地多)的时间和其他各种成本。

但是,对一个线程安全类进行扩展的时候就需要思考一些问题。

比如我们熟知的线程安全类Vector,该类中对所有的公有方法提供了synchronized修饰以保证访问互斥与可见性。

但Vector毕竟是一个公有的结构,他对客户代码的不变性约束一无所知。

比如客户代码中对某个Vector对象连续调用了两次方法,虽然每次都是线程安全的,但这种复合操作并不是一个原子操作,它可能不满足我们的不变性约束,于是线程安全类变得"不安全"了。

对于一种数据结构类,我们经常做put-if-absent操作。

当然,如果是在栈封闭或者是单线程应用的情况下这没什么问题。

但,如果是多线程访问同一个数据结构对象时我们就需要考虑这一操作是否是安全的?

即便我们使用的是所谓线程安全类。

于是我们会重用这个线程安全类,对其进行扩展,并保证我们的不变性约束不会受到破坏:

public class BetterVector <E> extends Vector<E> {
static final long serialVersionUID = -3963416950630760754L; public synchronized boolean putIfAbsent(E x) {
boolean absent = !contains(x);
if (absent)
add(x);
return absent;
}
}

也许是因为我们扩展的是JDK里面的Vector,所以会给人带来一种安全感(除了有些规范中定义的同步策略,其余的情况谁都无法保证)。

但如果是你的同事提供的线程安全类呢? 谁都无法保证它不会在下一个版本时发生变化。

比较要命的是下一个版本中发生变化的偏偏是同步策略,这导致子类直接受影响。

我们需要想一个问题,如果不去继承,我们如何在重用现有的线程安全类的情况下又保证自己的不变性约束?

于是我们想到了只使用需要的方法,既然继承很危险,那我把线程安全对象作为field,把需要的功能拿过来用就可以了。

但下面是一个错误例子:

class BadListHelper <E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}

不够细心的话,给人的第一反应就是:这没问题吧....

确实,我们使用的list已经用Collections.ysnchronizedList装饰过,何况我们提供的putIfAbsent也加上了synchronized关键字,这个方法确实是同步的。

但是某个线程调用putIfAbsent的时候,另一个线程也可以调用其他方法。

这样我们的不变性约束就被破坏,这个helper类变得毫无意义。

也就是说问题在于这个synchronized,我们要的不是synchronized(this),而是synchronized(list)。

因此helper应该改为:

class GoodListHelper <E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public boolean putIfAbsent(E x) {
synchronized (list) {
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
}

到这一步已经很不错了,但是派生类和基类仍然存在一些耦合。

也许我们可以将list声明为private final,并提供一个构造器,在构造器里用Collections.ysnchronizedList进行装饰。

但即使这样仍存在行为上的耦合,我们不能针对基类的行为为其添砖加瓦。

于是我们可以使用组合来解决这一问题:

public class ImprovedList<T> implements List<T> {
private final List<T> list; public ImprovedList(List<T> list) { this.list = list; } public synchronized boolean putIfAbsent(T x) {
boolean contains = list.contains(x);
if (contains)
list.add(x);
return !contains;
} public int size() {
return list.size();
} public boolean isEmpty() {
return list.isEmpty();
} public boolean contains(Object o) {
return list.contains(o);
} public Iterator<T> iterator() {
return list.iterator();
} public Object[] toArray() {
return list.toArray();
} public <T> T[] toArray(T[] a) {
return list.toArray(a);
} public synchronized boolean add(T e) {
return list.add(e);
} public synchronized boolean remove(Object o) {
return list.remove(o);
} public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
} public synchronized boolean addAll(Collection<? extends T> c) {
return list.addAll(c);
} public synchronized boolean addAll(int index, Collection<? extends T> c) {
return list.addAll(index, c);
} public synchronized boolean removeAll(Collection<?> c) {
return list.removeAll(c);
} public synchronized boolean retainAll(Collection<?> c) {
return list.retainAll(c);
} public boolean equals(Object o) {
return list.equals(o);
} public int hashCode() {
return list.hashCode();
} public T get(int index) {
return list.get(index);
} public T set(int index, T element) {
return list.set(index, element);
} public void add(int index, T element) {
list.add(index, element);
} public T remove(int index) {
return list.remove(index);
} public int indexOf(Object o) {
return list.indexOf(o);
} public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
} public ListIterator<T> listIterator() {
return list.listIterator();
} public ListIterator<T> listIterator(int index) {
return list.listIterator(index);
} public List<T> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex, toIndex);
} public synchronized void clear() { list.clear(); }
}

Java - 关于扩展线程安全类的一些思考的更多相关文章

  1. Java 并发编程(三)为线程安全类中加入新的原子操作

    Java 类库中包括很多实用的"基础模块"类.通常,我们应该优先选择重用这些现有的类而不是创建新的类.:重用能减少开发工作量.开发风险(由于现有类都已经通过測试)以及维护成本.有时 ...

  2. 那些年读过的书《Java并发编程实战》二、如何设计线程安全类

    1.设计线程安全类的过程 设计线程安全类的过程就是设计对象状态并发访问下线程间的协同机制(在不破坏对象状态变量的不变性条件的前提下). (1)构建线程安全类的三个基本要素: 1)找出构成对象状态的所有 ...

  3. Java线程:线程安全类和Callable与Future(有返回值的线程)

    一.线程安全类 当一个类已经很好的同步以保护它的数据时,这个类就称为线程安全的.当一个集合是安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候,第二个线程也来 ...

  4. Java视频扩展知识 线程池的了解

     Java视频扩展知识   线程池的了解 1.简单介绍: Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用.为我们在开发中处理线程的 ...

  5. Java并发编程(十三)在现有的线程安全类中添加功能

    重用现有的类而不是创建新的类,可以降低工作量,开发风险以及维护成本. 有时候线程安全类可以支持我们所有的操作,但更多时候,现有的了类只能支持大部分的操作,此时就需要在不破坏线程安全性的情况下添加一个新 ...

  6. JAVA并发-为现有的线程安全类添加原子方法

    JAVA中有许多线程安全的基础模块类,一般情况下,这些基础模块类能满足我们需要的所有操作,但更多时候,他们并不能满足我们所有的需要.此时,我们需要想办法在不破坏已有的线程安全类的基础上添加一个新的原子 ...

  7. Java并发编程之set集合的线程安全类你知道吗

    Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...

  8. Java并发3-多线程面试题

    1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速. 2) 线程和进程有什 ...

  9. Java实现的 线程池

    由于最近开始学习java,用到了线程池,按照之前c++的写法写出此java版的线程池 TaskRunnale实现相关任务的接口,具体要实现什么任务在相应的run函数中实现. package threa ...

随机推荐

  1. 新编辑器Cocos Creator发布:对不起我来晚了!

    1月19日,由Cocos创始人王哲亲手撰写的一篇Cocos Creator新品发布稿件在朋友圈被行业人士疯狂转载,短短数小时阅读量突破五位数.Cocos Creator被誉为“注定将揭开Cocos开发 ...

  2. lua 5.3 英文手册 自己收集整理版

    /* ** state manipulation */ LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);//创建lua虚拟机 LUA ...

  3. “全栈2019”Java第二十八章:数组详解(上篇)

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. iframe嵌套页面的跳转方式

    一.背景A,B,C,D都是jsp,D是C的iframe,C是B的iframe,B是A的iframe,在D中跳转页面的写法区别如下. 二.JS跳转window.location.href.locatio ...

  5. Mybatis映射.xml文件报错

    MyBatis框架里面,在dao层进行测试,控制台显示错误是:必须为元素类型 "delete" 声明属性 "resultType" 相应的.xml文件的sql语 ...

  6. 【随记】WPF中xaml输入中文乱码问题解决

    在Visual Studio中开发WPF应用程序时,在XMAL文档编写界面输入中文时变为乱码.可能的原因之一是VS中安装了VAssistX插件,导致编码冲突,使中文输入乱码.解决方法是在VAssist ...

  7. windows下webpack不是内部命令 解决方法

    安装webpack 到打包文件一路出现的各种问题 windows下webpack不是内部命令 安装完webpack后要加下环境变量 系统变量新建 NODE_PATH 变量值E:\demo\webpac ...

  8. archlinux安装tftp

    1. 安装  [guo@archlinux ~]$ sudo pacman -S tftp-hpa 2. 启用  [guo@archlinux ~]$ systemctl start tftpd.se ...

  9. 单元测试遇到的Mock重载方法问题

    测试某个异常抛出情况,单元测试输出为验证Logger的一条记录,该异常情况日志记录LogInfo,但是LogInfo中平时都用一个参数,在catch这个异常时调用了两个参数的重载方法,导致一直Mock ...

  10. $bzoj1021-SHOI2008\ Debt$ 循环的债务 $dp$

    题面描述 \(Alice\).\(Bob\)和\(Cynthia\)总是为他们之间混乱的债务而烦恼,终于有一天,他们决定坐下来一起解决这个问题.不过,鉴别钞票的真伪是一件很麻烦的事情,于是他们决定要在 ...