对象的共享

synchronized不仅可以保证原子性,确定临界区,还可以保证可见性

1.可见性

1.1失效数据

  • 如果对象无法保证可见性,那么在对象数据状态发生变化之后,其他线程无法获取对象属性的最新值,此时就会得到一个失效数据
  • 一个线程安全的类,不但set方法要使用synchronized,get方法同样要使用synchronized关键字,因为get方法要使用synchronized来保证可见性。如下面代码中的MutableInteger就不是一个线程安全类:
public class MutableInteger {
    private int value;

    public int getValue() {
        return value;
    }

    public synchronized void setValue(int value) {
        this.value = value;
    }
}

get方法使用synchronized后,即可保证这个类成为一个线程安全类:

public class MutableInteger {
    private int value;

    public synchronized int getValue() {
        return value;
    }

    public synchronized void setValue(int value) {
        this.value = value;
    }
}

1.2非原子的64位操作

  • 在线程没有同步的情况读取变量,可能会读取到一个失效值,但这至少是之前某个线程设置的值,而不是一个随机值,这种安全性保证称为最低安全性
  • 但是对于像Long````Double这种64位对象,在JVM进行读取、写入时,是拆分位两条指令进行操作的,因此对于它们的操作可能会产生不可预知的结果,对于这类对象,保证线程安全性必须使用volatile关键字或使用锁进行保护

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

1.3volatile

  • 当一个变量被声明为volatile时,编译器与运行时都会注意到这个变量是共享的,不会将对于此变量的操作与其他内存操作做重排序。volatile不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile变量时,总是可以读取到最新值
  • 加锁机制既可保证原子性,又可保证内存可见性,但是volatile只能保证内存可见性

满足以下条件时,才可使用volatile:
对变量的写入操作不依赖与变量的当前值,或者确认只有一个线程会对当前线程执行写入操作
该变量不会与其他状态变量一起纳入不变性条件中
在访问变量时不需要加锁

2.发布与逸出

发布是指使对象可以被当前作用域之外的代码所访问
当某个不该发布的对象被发布时,就称为逸出,例如在对象尚未实例化完成时,就将对象发布,此时就会造成逸出

3.线程封闭

如果数据只能在单线程内访问即只在当前线程中访问,那么数据就不需要同步,这种技术称为线程封闭

3.1Ad-hoc线程封闭

Ad-hoc线程封闭是指维护线程封闭性的职责完全由程序来实现
例如volatile变量存在一种特殊的线程封闭,只要能确保volatile变量只能被一个线程所修改,那么就可以保证变量的线程安全性。

3.2栈封闭

栈封闭是一种特殊的线程封闭,它全部使用局部变量将对象封装在线程中,保证线程安全性

3.4ThreadLocal类

ThreadLocal类是JDK提供的对于每个线程独有的存储区。但是不要滥用ThreadLocal,例如使用ThreadLocal来进行一些不必要的参数传递,既没有提高程序性能,反而会增加代码理解的复杂度

4不变性

如果某个对象被创建之后其状态就不可能被修改,那么这个对象就被称为不可变对象,不可变对象一定是线程安全的

满足以下条件时,对象才是不可变的:

  • 对象创建后其状态就不能修改
  • 对象的所有域都是final类型的
  • 对象是正确创建的(对象创建期间this引用没有逸出)

Final域能确保初始化过程的安全性,从而可以不受限制的访问不可变对象,并在共享这些对象时无需同步

5 安全发布

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

安全发布对象的方式:

  • 在静态初始化函数中初始化一个对象引用
  • 将对象引用保存到volatile类型域或者AtomicReference对象中
  • 将对象引用保存到某个正确构造的final类型域中
  • 将对象引用保存到一个由锁保护的域中

对象发布的原则:

  • 不可变对象可以通过任何机制来进行发布
  • 事实不可变对象必须通过安全方式来进行发布
  • 可变对象必须通过安全方式来进行发布,并且必须是线程安全的或者由某个锁来进行保护

注 : 事实不可变对象 指对象是可变的,但是其状态在对象发布后在实际运行过程中不会发生变化

你可能感兴趣的:(对象的共享)