【学习笔记】【Java并发编程实战】第三章 对象的共享

可见性

  • 在没有同步的情况下,编译器、处理器以及运行时都可能对操作的执行顺序进行一些意想不到的调整。
  • 除非在每次访问变量时都使用同步,否则可能获得该变量的一个失效值,一个线程可能获得某个变量的最新值,而获得另一个变量的失效值。
  • Java内存模型要求变量的读取操作和写入操作都必须是原子操作,但对于非volatile类型的long和double变量,JVM允许将64位的读写操作分解为两个32位的操作。
  • 为了确保所有线程都能看到共享变量的最新值,所有执行读写操作的线程都必须在同一个锁上同步。
  • 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
  • 从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量就相当于进入同步代码块。
  • volatile变量的典型用法:检查某个状态标记以判断是否退出循环。
volatile boolean asleep;
...
	while(!asleep)
		countSomeSheep();
  • 加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。

发布与逸出

  • 发布:使对象能够在当前作用域之外的代码中使用。当发布一个对象时,在该对象的非私有域中引用的所有对象同样会被发布。
  • 逸出:某个不应该发布的对象被发布。
  • 外部方法:行为并不完全由当前类来规定的方法,包括其它类中定义的方法以及当前类中可以被改写的方法。
  • 当把一个对象传递给某个外部方法时,就相当于发布了这个对象。
  • 当从对象的构造函数中发布对象时,只是发布了一个尚未构造完成的对象,即使发布对象的语句位于构造函数的最后一行也是如此。
  • 在构造过程中使this引用溢出的一个常见错误:在构造函数中启动一个线程。在构造函数中创建线程并没有错误,但不要立即启动它,而是通过一个start或initialize方法来启动。
public class SafeListener{
    private final EventListener listener;
    
    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }
    
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener(); // 使用工厂方法来防止this引用在构造过程中逸出
        source.registerListener(safe.listener); // SafeListener在构造完后才能被source类的方法调用
        return safe;
    }
}

线程封闭

  • 线程封闭:不共享数据,仅在单线程内访问数据,就不需要同步。
  • Ad-hoc线程封闭:维护线程封闭性的职责完全由程序实现来承担。
  • 栈封闭(线程内部使用):只有通过局部变量才能访问对象,局部变量的固有属性之一就是封闭在执行线程中。
  • ThreadLocal类:ThreadLocal类似于Map,使线程中的某个值与保存值得对象关联起来,提供get()和set()方法,get总是返回当前执行线程调用set时设置的最新值。

不变性

  • 不可变对象一定是线程安全的,它们的不变性条件是由构造函数创建的。
  • 不可变对象的条件:
    1. 对象创建以后其状态就不能修改;
    2. 对象的所有域都是final类型;
    3. 对象的创建期间this引用没有逸出。
  • final域在初始化时必须赋值,并不再修改,因此能确保初始化过程的安全性,从而可以不受限制地访问不可变对象。

安全发布

  • 要安全地发布一个对象,对象的引用以及对象的状态必须同时对其它线程可见。一个正确构造的对象可以通过以下方式来安全地发布:
    1. 在静态初始化函数中初始化一个对象引用;
    2. 将对象的引用保存到volatile类型的域或者AtomicReference对象中;
    3. 将对象的引用保存到某个正确构造对象的final类型域中;
    4. 将对象的引用保存到一个由锁保护的域中。
  • 不可变对象可以通过任意机制来发布;事实不可变对象必须通过安全方式来发布;可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。
  • 使用和共享对象时的策略:
    1. 线程封闭,对象只能由一个线程拥有和修改;
    2. 只读共享,可以由多个线程并发访问但任何线程都不能修改它;
    3. 线程安全共享,对象在其内部实现同步,线程通过公有接口来进行访问;
    4. 保护对象,被保护的对象只能通过持有特定的锁来访问。

你可能感兴趣的:(Java)