上一篇文章说的是,避免多个线程在同一时间访问对象中的同一数据,这篇文章来详细说说共享和发布对象。
在没有同步的情况下,我们无法预料编译器、处理器安排操作执行的顺序,经常会发生以为“一定会”发生的动作实际上没有发生。可以用一些简单的方法来避免这个问题。
?
在 Java 中,如果不是64位版本的,JVM 会把 double 或者 long 的读和写划分在两个 32 位中,这样一来,在多线程中,没有声明是 volatile 的 double 或者 long 也是不安全的。
?
锁是同步和互斥的,同样也是内存可见的。为了避免出现读到过期的数据,读和写的线程都要使用公共的锁进行同步。
?
volatile 是一种弱同步,只能保证可见性,而不能保证原子性。(为了安全,可以理解成 volatile 基本上只能使 boolean 的值原子化,像自增这种操作是不能被?volatile?原子化的)在确保?volatile?变量所引用状态的可见性、标识重要的生命周期事件(初始化或者关闭)的时候可以用,其他的时候最好不要用。下面数绵羊的代码就是一种典型的应用:
?
class="java" name="code">volatile boolean asleep; ... while(!asleep) { count(); }
如果有其他的线程修改了 asleep, 让它变成 true 了,那么就会跳出循环。
?
不要在构造函数中启动线程,有可能造成溢出。用单独的 start() 或者 init() 启动线程
?
对于只在一个线程内使用的数据就没必要同步了,对于可以用 volatile(只有一个线程写入的变量),或者可以用栈限制:
?
public in loadTheArk(Collection<Animal> cadidates) { SortedSet<Animal> animals; int numPairs = 0; Animal candidate = null; // new 了一个集合来装 cadidates,避免溢出 animals = new TreeSet<>(new SpeciesGenderComparator()); animals.addAll(candidates); ... // count numPairs }
?也可以用更规范的方法 ThreadLocal 把线程和持有数值的对象关联在一起。但是这种方法开销比较大。
?
不可变的对象是线程安全的,它必须满足:
?
对象发布时,应该使用同步。但是对于不可变对象来说,可以不用同步。为了安全的发布对象,可以:
?
如果对象本身是可变的,但是发布之后状态不会被修改,那么就在发布的时候对所有线程可见,之后线程访问这个对象就不需要额外的同步了。如果后续状态会被改变,那么必须是线程安全或者锁保护的。
?
?
?
?
?