对象的共享

要编写正确的并发程序,关键在于:在访问共享的可变状态时需要进行正确的管理。

1.可见性

为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

重排序: 多个线程操作的时候没有按照程序的定义的顺序来执行。只要在某个线程中无法检测到重排序的情况,那么就无法确保线程中的操作按照程序中指定的顺序来执行。

2.失效数据

a, 缺乏同步的程序中可能产生错误失效数据。

b,最低安全性:线程在没有同步情况下读取变量值,得到一个失效值,但这个值是由之前某个线程设置的值,而不是随机值,这种安全保证称为最低安全性。

c,非原子操作64位:非volatile类型的long和double变量,JVM允许将64位的读操作和写操作分解为两个32位的原子操作,若读取一个非voltatile类型的long变量时,如果该变量读和写不在同一个线程中执行,那么可能读到高32和低32位。在多线程中使用共享且可变的long,double等类型变量也是不安全的,除非使用volatile,或者使用锁。

d,加锁与可见性

 

当线程B执行由锁保护的同步代码时,可以看见线程A之前在同一个同步代码块中所有的所有操作。如果没有同步,就无法保证。

e,voltatile变量

volatitle: 用来确保将变量的更新操作通知到其他线程,把变量声明为volatile,编译时与运行时都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起重排,volatile变量不会被缓存在寄存器或者其他处理器看不见的地方,因此volatile变量总会返回最新写入的值。

从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,读取volatile变量相当于退出同步代码块。

仅当volatile变量能简化代码的实现及对同步策略的验证时,才应使用。如果在验证正确性进行复杂判断就不要使用volatile变量。

volatile变量的正确使用方式包括:确保他们自身状态的可见性,确保所引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生(如:初始化和关闭)。

加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。

满足一下所有条件时,才应使用volatitle变量:

a, 对变量的写入操作不依赖变量当前值,或者确保只有单个线程更新变量值。

b, 该变量不会与其他状态变量一起纳入不变性条件。

c, 在访问变量时不需要加锁。 

 3,发布与逸出

发布一个对象:使对象能够在当前作用域之外的代码中使用;在某个非私有的方法中返回该引用,或者将引用传递到其他类中。

常见做法:将对象的引用保存到一个共有静态变量中,以便任何类和线程都能看到该对象。

间接发布对象

封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏设计约束条件变得更难。

逸出:当某个不应该发布的对象被发布时。

4,安全的对象构造过程

不要在构造过程中使用this引用逸出。

5,线程封闭

不共享数据,仅在单线程内访问数据,就不需要同步,这种技术是线程同步。

a,ad-hoc:维护线程封闭性的职责完全由程序实现来承担。

b,栈封闭:只有通过局部变量才能访问对象。由于任何方法都无法获得对基本类型的引用,这种机制确保了基本类型的局部变量始终封装在线程内。

 c,使线程中的某个值与保存值的对象关联起来。经常用于防止对可变的单实例或全局变量进行共享。

6,不变性

不可变对象:某个对象在被创建后其状态就不能被修改,为不可变对象。不可变对象一定是线程安全的。

不可变性:不等于将对象中的所有域都声明为final,在final类型域中可以保存对可变对象的引用。

不可变对象:对象创建以后状态不可变,对象的所有域都是final类型,对象是正确创建的。

a,final域,除非需要更高的可见性,否则应将所有域都声明为私有域,除非需要某个域是可变的,否则应将其声明为final域。

b,使用volatile类型来发布不可变对象。

7,安全发布

a,不正确发布:如果没有足够的同步,那在多个线程间共享数据将发生一些奇怪的事情。

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

c,安全发布的常用模式:

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

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

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

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

d 事实不可变性 : 在没有额外的同步的情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。

e 可变对象 :要安全地发布共享可变对象,这些对象就必须被安全的发布,并且是线程安全或者由某个锁保护起来。

对象发布取决于他的可变性:

(1)不可变对象可以通过任意机制来发布。(2)事实不可变对象必须通过安全方式来发布。(3)可变对象必须通过安全方式来发布,并且必须是线程安全或者由某个锁保护起来。

f, 安全地共享对象。

并发程序和共享对象时,可以使用一些策略:

a,线程封闭 : 线程封闭的对象只能由一个线程拥有,对象被封闭在线程中。

b,只读共享:没有额外同步时,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享只读包括不可变对象和事实不可变对象。

c,线程安全共享:线程安全的对象在其内部实现同步,多个线程可以通过对象的公共接口来访问。

d,保护对象:被保护的对象只能通过持有特定对象来访问,保护对象包括,封装在其他线程安全对象中的对象,以及已发布的并由某个特定锁保护的对象。

 posted on 2018-12-31 20:02  岂曰-无衣  阅读(150)  评论(0编辑  收藏  举报