7-2 线程安全

一、什么是线程安全

要求ADT或方法在多线程中要执行正确。要求不违反spec,与线程如何调度无关,且不能为了线程安全而要求client满足某些要求。

迭代器不是线程安全的,不能同时remove和迭代。

**线程不安全的本质:**多个线程都去修改、访问某个共享的数据。

保证线程安全的四个方法:限制数据共享,共享不可变数据,共享线程安全的可变数据,同步机制。

二、限制数据共享

1、将可变数据限制在单一线程内部,避免竞争。

JVM内存机制:每个线程有自己的栈,且仅能访问自己的栈。基本的局部变量保存在线程栈中,对象类型的数据保存在堆中(使用new创建的)。对象的成员变量在堆,对象的方法、方法的局部变量在栈。堆中的对象是所有线程共享的。

下图的两个线程将result限制在了线程内部,不会有竞争:

在这里插入图片描述

snapshot如下:

在这里插入图片描述

2、避免全局变量

例如下图的代码,如果两个线程的1和1,1和2,1和3同时执行都会有问题,可能创建多个实例:

在这里插入图片描述

下图的代码中,变量amountA、amountB、amount作为局部变量被限制在单一线程中,而cashLeft作为全局变量,被所有线程共享,会危害线程安全:

在这里插入图片描述

如果一个ADT有多个可变的域并且可以对其进行改变,那么就很难用上述策略保证线程安全,即使private final也不行,因为每个线程仍然是可以访问到的。

三、共享不可变数据

允许有全局的变量,但要使用不可变数据类型、不可变引用。

这里的不可变数据类型和通常所说的不可变数据类型有差异。通常说的不可变数据类型只需要对于外部一直表现一个不变的值即可,却可以允许内部出现一些有益的变化;而这里要保证线程安全的不可变数据类型,是要连内部有益的变化也不允许有。

如果要从immutable角度来保证一个ADT是线程安全的,要做到四点:1、没有mutator方法;2、所有的域是private final的;3、没有表示泄露;4、没有任何可变的对象(有益的可变也没有)。

要确保一个ADT是不可变且线程安全的,需要检查以下除了客户端相关的方面:

在这里插入图片描述

四、共享线程安全的可变数据

如果要使用mutable的数据类型,可以用线程安全的数据类型。一般JDK会提供两个功能一模一样的类,一个线程安全一个不安全,在spec中会有详细说明。

集合类都不是线程安全的,因为那些对集合进行修改的操作不是原子的,操作间会交错。集合类有相应的decorator,这些decorator最好直接如下图用匿名的方式添加,这样防止有原hashmap的别名存在:

在这里插入图片描述

线程安全的数据类型是让每一个操作都变成原子的了,所以两个相同的操作之间不再存在竞争,但不同的操作之间仍然可能存在竞争,这时如果程序是线程安全的,要进行说明。

例如这个使用缓存机制判断素数的例子,containsKey、get、put这三个操作自己和自己不会再有竞争,然后containsKey和get之间的竞争是无害的,containsKey和put之间的竞争不会导致错误结果:
在这里插入图片描述

例子,下图的变量中,哪些是线程安全的?

在这里插入图片描述

五、论证线程安全

在代码中以注释的形式说明,ADT使用了什么方法来保证线程安全。最前面说了四种方法,如果采用的是后两种,还要说明语句之间不存在交错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值