Java 并发之共享对象

上一篇文章说的是,避免多个线程在同一时间访问对象中的同一数据,这篇文章来详细说说共享和发布对象。

在没有同步的情况下,我们无法预料编译器、处理器安排操作执行的顺序,经常会发生以为“一定会”发生的动作实际上没有发生。可以用一些简单的方法来避免这个问题。

在 Java 中,如果不是64位版本的,JVM 会把 double 或者 long 的读和写划分在两个 32 位中,这样一来,在多线程中,没有声明是 volatile 的 double 或者 long 也是不安全的。

锁是同步和互斥的,同样也是内存可见的。为了避免出现读到过期的数据,读和写的线程都要使用公共的锁进行同步。

volatile 是一种弱同步,只能保证可见性,而不能保证原子性。(为了安全,可以理解成 volatile 基本上只能使 boolean 的值原子化,像自增这种操作是不能被 volatile 原子化的)在确保 volatile 变量所引用状态的可见性、标识重要的生命周期事件(初始化或者关闭)的时候可以用,其他的时候最好不要用。下面数绵羊的代码就是一种典型的应用:

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 把线程和持有数值的对象关联在一起。但是这种方法开销比较大。

不可变的对象是线程安全的,它必须满足:

  1. 它的状态在创建后不能被修改
  2. 所有的域都是 final 的
  3. 被正确的创建(创建期间没有发生 this 引用的溢出)

对象发布时,应该使用同步。但是对于不可变对象来说,可以不用同步。为了安全的发布对象,可以:

  • 静态初始化对象的引用
  • 把它的引用存储到 volatile 域或者 AtomicReference
  • 把它的引用存储到正确创建对象的 final 域中
  • 把它的引用存储到由正确锁保护的域中

如果对象本身是可变的,但是发布之后状态不会被修改,那么就在发布的时候对所有线程可见,之后线程访问这个对象就不需要额外的同步了。如果后续状态会被改变,那么必须是线程安全或者锁保护的。

原文地址:https://www.cnblogs.com/vivizhyy/p/3394865.html