没有合适的资源?快使用搜索试试~ 我知道了~
Java多线程编程的线程安全性.docx
1.该资源内容由用户上传,如若侵权请联系客服进行举报
2.虚拟产品一经售出概不退款(资源遇到问题,请及时私信上传者)
2.虚拟产品一经售出概不退款(资源遇到问题,请及时私信上传者)
版权申诉
0 下载量 31 浏览量
2022-07-02
22:21:49
上传
评论
收藏 218KB DOCX 举报
温馨提示
试读
12页
使用一个类的时候我们必须先弄清楚这个类是否是线程安全的。因为这关系到我们如何正确使用这些类。Java标准库中的一些类如ArrayList、HashMap和SimpleDateFormat,都是非线程安全的,在多线程环境下直接使用它们可能导致一些非预期的结果,甚至是一些灾难性的结果。一般来说,Java标准库中的类在其API文档中会说明其是否是线程安全的(没有说明其是否是线程安全的,则可能是也可能不是线程安全的)。
资源推荐
资源详情
资源评论
Java 多线程编程的线程安全性
一.线程安全性
一般而言,如果一个类在单线程环境下能够运作正常,并且在多线程环境下,在
其使用方不必为其做任何改变的情况下也能运作正常,那么我们就称其是线程安
全的。反之,如果一个类在单线程环境下运作正常而在多线程环境下则无法正常
运作,那么这个类就是非线程安全的。因此, 一个类如果能够导致竞态,那么
它就是非线程安全的;而一个类如果是线程安全的,那么它就不会导致竞态。下
面是《Java 并发编程实战》一书中给出的对于线程安全的定义:
>
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者
这些线程将如何交替执行,并且在代码中不需要任何额外的同步或
协同,这个类都能表现出正确的行为,那么就称这个类是线程安全
的。
使用一个类的时候我们必须先弄清楚这个类是否是线程安全的。因为这关系到我
们如何正确使用这些类。Java 标准库中的一些类如 ArrayList、HashMap 和
SimpleDateFormat,都是非线程安全的,在多线程环境下直接使用它们可能导
致一些非预期的结果,甚至是一些灾难性的结果。一般来说,Java 标准库中的
类在其 API 文档中会说明其是否是线程安全的(没有说明其是否是线程安全的,
则可能是也可能不是线程安全的)。
从线程安全的定义上我们不难看出,如果一个线程安全的类在多线程环境下能够
正常运作,那么它在单线程环境下也能正常运作。既然如此,那为什么不干脆把
所有的类都做成线程安全的呢?是否将一个类做成线程安全的,从某种程度上来
说是一个设计上的权衡的结果或决定:一方面,一个类是否需要是线程安全的与
这个类预期被使用的方式有关,比如,我们希望一个类总是只能被一个线程独自
使用,那么就没有必要将这个类做成线程安全的。其次,把一个类做成线程安全
的往往是有额外代价的。
一个类如果不是线程安全的,我们就说它在多线程环境下直接使用存在线程安全
问题。线程安全问题概括来说表现为 3 个方面:原子性、可见性和有序性。
二.原子性
原子的字面意思是不可分割的。对于涉及共享变量访问的操作,若该操作从其执
行线程以外的任意线程来看是不可分割的,那么该操作就是原子操作,相应地我
们称该操作具有原子性。所谓“不可分割”,其中一个含义是指访问某个共享变量
的操作从其执行线程以外的任何线程来看,该操作要么已经执行结束要么尚未发
生,即其他线程不会“看到”该操作执行了部分的中间效果。
在生活中我们可以找到的一个原子操作的例子就是人们从 ATM 机提取现金:尽
管从 ATM 软件的角度来说,一笔取款交易涉及扣减户账户余额、吐出钞票、新
增交易记录等一系列操作,但是从用户的角度来看 ATM 取款就是一个操作。该
操作要么成功了,即我们拿到现金(账户余额会被扣减)这个操作发生过了;要
么失败了,即我们没有拿到现金,这个操作就像从来没有发生过一样(账户余额
也不会被扣减)。除非 ATM 软件有缺陷,否则我们不会遇到吐钞口吐出部分现
金而我们的账户余额却被扣除这样的部分结果。
总的来说,Java 中有两种方式来实现原子性。一种是使用锁(Lock)。锁具有
排他性,即它能够保障一个共享变量在任意时刻只能够被一个线程访问。这就排
除了多个线程在同一时刻访问同一个共享变量而导致干扰与冲突的可能,即消除
了竞态。另一种是利用处理器提供的专门 CAS(Compare- and- Swap)指令。
CAS 指令实现原子性的方式与锁实现原子性的方式实质上是相同的,差别在于
锁通常是在软件这一层次实现的,而 CAS 是直接在硬件(处理器和内存)这一
层次实现的,它可以被看作“硬件锁”。
在 Java 语言中,long 型和 double 型以外的任何类型的变量的写操作都是原子
操作,即对基础类型(long、double 除外)的变量和引用型变量的写操作都是
原子的。这点是由 Java 语言规范(Java Language Specification)规定,由 Java
虚拟机具体实现。一个 long/double 型变量的读/写操作在 32 位 Java 虚拟机下
可能会被分解为两个子步骤(比如先写低 32 位,再写高 32 位)来实现,这就
导致一个线程对 long/double 型变量进行的写操作的中间结果可以被其他线程
所观察到,即此时针对 long/double 类型的变量的访问操作不是原子操作。尽管
如此,Java 语言规范特别地规定对于 volatile 关键字修饰的 long/double 类型变
量的写操作具有原子性。因此,我们只需要用 volatile 关键字(下一篇文章会进
一步介绍该关键字)修饰可能被多个线程访问的 long/double 类型的变量,就可
以保障对该变量的写操作的原子性。
三.可见性
在多线程环境下,一个线程对某个共享变量进行更新之后,后续访问该变量的线
程可能无法立刻读取到这个更新的结果,甚至永远也无法读取到这个更新的结果。
这就是线程安全问题的另外一个表现形式:可见性。
下面看一个可见性的例子:
// Code 2-2
public class VisibilityDemo {
public static void main(String[] args) {
UselessThread uselessThread = new UselessThread();
uselessThread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
uselessThread.cancel();
}
}
class UselessThread extends Thread {
private boolean cancelled = false;
@Override
public void run() {
System.out.println("Task has been started.");
while (!cancelled) {}
System.out.println("Task has been cancelled.");
}
public void cancel() {
cancelled = true;
}
}
剩余11页未读,继续阅读
资源评论
小兔子平安
- 粉丝: 209
- 资源: 1940
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功