3.1 可见性
synchronized 不仅实现了原子性操作或者确定了临界区,而且确保内存可见性。
*****必须在同步中才能保证:当一个线程修改了对象状态之后,另一个线程可以看到发生的状态变化。
1.失效值问题
以上类非线程安全,get和set在非同步情况下获取value值。
当一个线程修改value,另一个线程可能得到更新后的值,也可能得不到。
对get和set进行同步,可以是成为线程安全类。
2. long或者double 需要用volatile修饰或者用锁保护。 因为64位值可能被拆为2个32位操作。
3.volatile 修饰变量,确保获取最新值。
加锁可以确保可见性和原子性,而volatile只能确保可见性。
可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态:
- 对变量的写操作不依赖于当前值。
- 该变量没有包含在具有其他变量的不变式中。
lower upper非独立于其他状态
3.2发布与逸出
发布:对象能够在当前作用域之外的代码中使用。
逸出: 某个不应该被发布的对象被发布。
3.3线程封闭
将某个对象封闭在某个线程中。举例:connection从线程池拿出给某个线程使用,且不会在分给其他线程。
java及核心库提供支持:例如 局部变量和ThreadLocal
但是程序员仍要确保封闭在线程中的对象不会从线程中逸出。
2栈封闭
局部变量的固有属性,就是封闭在执行的栈中。其他线程无法访问这个栈。但是编程要确保不逸出。
开发人员要给出注释那些对象需要封闭到执行线程中,便于以后维护时不会造成逸出。
3.threadlocal
3.4不变性
不可变对象一定是线程安全的。
满足以下条件时,对象才不可变:
1.对象创建以后其状态就不能修改。
2.对象的所有域都是final类型。
3.对象是正确创建的(对象的创建期间,this引用没有逸出)。
1.Final域
2.volatile类型来发布不可变对象(volatile+不可变对象)
当需要对一组数据以原子方式执行某些操作时,可以考虑创建一个不可变类来封装这些数据。如上例。
对于在访问和更新多个相关变量时出现的竞争性条件,通过将这些变量保存在一个不可变类中来消除。不可变对象,当线程获得该变量引用后,不必担心其他线程会修改该对象的状态。如果要更新该对象,可以创建 一个新的容器对象 。其他线程没有影响。
总结:将变化的封装在一个不变的容器类中,讲内容的变化改为创建一个新的容器。
3.5安全发布
上面讨论的是如何让对象不被发布,讲对象封闭在线程或者另一个对象中。但是有时需要在线程中共享对象,所以需要安全发布对象。
//在没有足够同步的情况下发布对象,不要这么做
public Holder holder;
public void intialize() {
holder = new Holder(42);
}
存在可见性问题,其他线程看到未被完全创建的对象。
1.不正确的发布:正确的对象被破坏
class Holder{
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if( n != n) {
throw new AssertionError("this statement is error");
}
}
}
2.安全发布的常用模式:
3.事实不可变对象:
如果对象的状态在发布后不会在改变,称为事实不可变状态。
*********
线程安全共享:此类内部实现同步。
保护对象:使用时加锁。