对象的共享
构建稳健的并发程序必须要正确的使用线程和锁
编写线程代码的核心在于要对状态访问操作进行管理,
特别是对共享的和可变的状态的访问
对象的状态是指存储在状态变量()中的数据例如实例或静态域
1. 可见性
重排序:在没有同步的情况下,编译器,处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的
调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得出正确的结论。
后果:某线程的操作无法按照程序中指定的顺序来执行
内置锁还可以确保共享变量的可见性
volatile:更轻量级的同步机制,不会被重排序。
volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,总会返回最新写入的值
当且仅当满足以下所有条件时,才应该使用volatile变量:
- 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
- 该变量不会与其他状态变量一起纳入不变性条件中
- 在访问变量时不需要加锁
2. 发布与溢出
发布(Publish):使对象能够在当前作用域之外的代码中使用。
溢出(Escape):某个不应该发布的对象被发布时。
3. 线程封闭
- Ad-hoc线程封闭
维护线程封闭性的职责完全由程序实现来承担。非常脆弱,避免使用 - 栈封闭
只能局部变量才能访问对象。 - ThreadLocal类
ThreadLocal类能使线程中的某个值和保存值的对象关联起来。提供get与set等访问接口或方法,这些方法为每个使用该变量的线程都存着一份独立的副本,因此get总是返回由当前线程在调用set时设置的最新值。
4. 不变性
不可变对象一定是线程安全的
当满足以下条件时,对象才是不可变的:
- 对象创建以后其状态就不能修改。
- 对象的所有域都是final类型。
- 对象是正确创建的(在对象的创建期间,this引用没有溢出)。
5. 安全发布
要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的
对象可以通过以下方式来安全地发布:
- 在静态初始化构造函数中初始化一个对象引用。
- 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
- 将对象的引用保存到某个正确构造对象的final类型域中。
- 将对象的引用保存在一个由锁保护的域中。
在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:
线程封闭。线程封闭的对象只能有一个线程拥有,对象被封闭在该线程中,并且只能有该线程修改。
只读共享。在没有额外同步的情况下,共享的只读对象可以有多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
线程安全共享。线程安全的对象在其内部实现同步,因此多线程可以通过对象的公有接口来进行访问而不需要进一步的同步。
保护对象。被保护的对象只能通过持有该特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。