《Java并发编程实战》学习笔记之 第3章 对象的共享

1.内存可见性

    synchronized关键字同步有两方面的作用:

 (1)实现原子性或者确定临界区

(2)确保内存可见性

所谓内存可见性,即当一个线程修改了对象状态后,其他线程能够看到修改后的状态。

     多线程程序在没有同步的情况下,编译器、处理器及运行时等都可能对操作执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得出正确的结论。

1.1 失效数据

比如线程在没有同步的情况下读取变量时得到的可能是失效数据(之前某个线程设置的值)。

1.2 非原子的64位操作

非volatile的64位数值变量,比如long,double,JVM允许将64位的读操作或写操作分解为两个32位的操作。如果对该变量的读操作和写操作在不同的线程中执行,那么很可能读取到某个值的高32位和另一个值的低32位而导致错误。

1.3加锁与可见性

加锁的含义不仅包括互斥行为,还包括内存可见性,为了确保所有线程都能看到共享变量的最新值,所有执行读或写操作的线程都必须在同一个锁上同步。

1.4 volatile变量

volatile变量是一种稍弱的同步机制,用来确保将变量的更新操作通知到其他线程。

volatile变量只能确保可见性,而不能确保原子性。

2.发布与逸出

发布一个对象的意思是指,是对象能够在当前作用域之外的代码中使用。比如把该对象放到一个共有静态变量中,或者在非私有的方法中返回该对象的引用。

当某个不应该被发布的对象被发布时,这种情况称为逸出。

不要在对象构造过程中使this引用逸出,对象构造函数完成之前不要显示或隐式使用当前对象的引用。

3.线程封闭

 3.1 Ad-hoc线程封闭

维护线程封闭的职责完全由程序实现来承担,比如只有单个线程对共享的volatile变量执行写入操作,即将写操作封闭在单个线程中以防止发生竞态条件,并且volatile变量的可见性还保证了其他线程能看到最新的值。

3.2 栈封闭

即使用局部变量,线程外部无法访问到局部变量。

3.3 ThreadLocal类

ThreadLocal对象能使线程中的某个值与保存的对象关联起来,ThreadLocal提供了get与set等访问接口或方法,为每个使用该变量的线程都存有一份独立的副本,get总是返回由当前执行线程在调用set时设置的最新值。

ThreadLocal对象通常用于防止可变的单实例变量或全局变量进行共享。

ThreadLocal类实现原理简要描述:

ThreadLocal类对象的值在初始化时,创建一个ThreadLocal.ThreadLocalMap对象,放在当前线程的threadLocals属性中,其中threadLocals的key是当前ThreadLocal对象,value是当前ThreadLocal对象的值。

 4.不变性

 不可变对象一定是线程安全的。

 不可变对象需要满足的条件:

对象创建以后其状态就不能修改;

对象的所有域都是final类型;

对象是正确创建的(在对象的创建期间,this引用没有逸出)。

4.1 final域

final域不能修改,在java内存模型中,final还有特殊的语义:能确保初始化过程的安全性,从而可以不受限制的访问不可变对象,并在共享这些对象是无需同步。

4.2 使用volatile类型发布不可变对象

对于访问和更新多个变量时出现的竞争条件问题,可以通过将这些变量封装到一个不可变对象中,并使用volatile修饰变量确保可见性,而不必担心其他线程会修改对象的状态。

5.安全发布

不要在没有足够同步的情况下发布可变对象。

5.1 没有足够的同步,当在多线程间共享数据时将发生一些非常奇怪的事情。

5.2不可变对象与初始化安全性

任何线程都可以在不需要额外同步的情况下安全的访问不可变对象,即使在发布这些对象时没有使用同步。

这种保证还将延伸至这些对象的所有final域,在没有额外同步的情况下可以安全的访问final类型(final类型的域执行非可变对象的情况下)的域。

 5.3 安全发布常用模式

 要安全的发布一个对象,对象的引用及状态必须同时对其他线程可见,一个正确构造的对象可以通过以下的方式来安全的发布:

(1)在静态初始化函数中初始化一个对象引用。

(2)将对象的引用保存到volatile类型的域或者AutoReferance对象中。

(3)将对象的引用保存到某个正确构造对象的final类型域中。

(4)将对象的引用保存到一个由锁保护的域中。

通常要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化器:

public static Holder holder = new Holder (66);

静态初始化器由JVM在类的初始化阶段进行,由于在JVM内部存在同步机制,因此通过这种方式初始化的对象都可以被安全的发布。

  5.4 事实不可变对象

如果对象在发布后不会被修改,那么对于其他在没有额外同步的情况下安全的访问这些对象的线程来说,安全发布是足够的。

所有的安全机制都能够确保,当对象的引用对所有访问该对象的线程可见时,对象发布时的状态对所有线程也将是可见的,如果对象状态不会再改变,那么就足以确保任何访问都是线程安全的。

如果对象从技术上来看是可变的,但其状态在发布后不会再改变,这种对象称为“事实不可变对象”,在这类对象发布之后,程序只需将它视为不可变对象即可。

  5.5 可变对象

 如果对象在构造后可以修改,那么安全发布只能确保发布当时的状态可见性,所以对于可边对象,不仅在发布时需要同步,而且在每次访问时也需要使用同步来确保后续操作的可见性。

对象的发布需求取决于它的可变性:

不可变对象可以通过任意机制来发布;

事实不可变对象必须通过安全方式来发布;

可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。

猜你喜欢

转载自albert2012.iteye.com/blog/2413120