3.1可见性
为了确保多个线程之间对内存写入的可见性,就必须使用同步机制
在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到调整。在缺乏足够同步的多线程程序中,想要对内存操作的执行顺序进行判断,几乎无法做出正确的结论
3.1.1失效数据
3.1.2非原子的64位操作
即使不考虑失效数据问题,在多线程环境下使用共享且可变的long和double等类型的变量也是不安全的,除非用volatile来声明或者用锁来保护
3.1.3加锁和可见性
加锁的含义不仅仅是互斥行为,还包括内存可见性,为了确保所有线程都能够看到修改后的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上进行
3.1.4Volatile
编译器和运行时都会注意到这个变量时共享的,因此不会将该变量上的操作与其他操作一起重排序
不会造成线程阻塞,是一种比Synchronized更轻量级的锁机制
volatile变量不会被缓存到寄存器或者其他处理器不可见的地方
仅当volatile变量能够简化代码的实现以及对同步策略的验证时,才应该使用它们
加锁机制既能保证原子性又能保证可见性,而volatile只能保证可见性
当且仅当满足以下条件时,才使用volatile
a.当对变量的写入操作不依赖当前变量的值,或者你能够确保只有单个线程更新变量的值
b.该变量不会与其他变量一起纳入不变性条件中
c.在访问变量时不需要加锁
3.2发布与逸出
发布一个对象的意思是指使对象能够在当前作用域之外的代码使用
例如:
将一个指向对象的引用放到其他代码可以访问的地方
一个非私有的方法中返回对象引用
引用传递到其他类的方法
当某个不应该被发布的对象被发布时,就叫做逸出
发布对象最简单的方式就是讲对象的引用保存到一个公有的静态变量中
当发布某个对象时,可能间接的发布其他对象
当发布某个对象时,该对象的非私有域中的所有对象同样会被发布
当把某个对象发布到外部方法时,就相当于发布了这个对象
发布一个内部的类实例也可以发布对象或内部状态
安全的对象构造过程:
不要再构造过程中使用this引用逸出
使this引用逸出最常见错误是,在构造函数中启动一个线程
使用私有构造函数和一个公共工厂方法防止this引用在构造函数中逸出
3.3线程封闭
如果仅在单线程内访问数据,就不需要同步,这种技术叫做线程封闭
应用场景
Swing中大量使用
JDBC的Connection对象
机制
局部变量
ThreadLocal
3.3.1Ad-hoc线程封闭
定义:指维护线程封闭性的职责完全由程序实现来承担
由于Ad-hoc线程封闭的脆弱性,因此程序中尽量少用
3.3.2栈封闭
在栈封闭中,只能通过局部变量访问对象
对于基本类型的局部变量,无论如何都 不会破坏栈的封闭性
在维持对象引用的栈封闭时,要确保引用对象不会逸出
3.3.3ThreadLocal类
通常用户对可变的单实例对象或者全局变量进行共享
3.4不变性
不可变对象一定是线程安全的
对象不可变应该满足的条件
a.对象创建以后其状态就不能修改
b.对象的所有域嗾使final
c.对象时正确创建的(在对象创建期间,this引用没有逸出)
在不可变对象的内部仍可以用可变的对象来管理他们的状态
3.4.1Final域
除非需要更高的可见性,否则将对象所有域声明为私有域是一个良好的编程习惯
除非需要某个域是可变的,否则将其声明为final也是一个良好的编程习惯
3.4.2示例:使用volatile类型来发布不可变的对象
3.5安全发布
3.5.1不正确的发布:正确的对象被破坏
3.5.2不可变对象和初始化安全性
任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步
3.5.3安全发布的常用模式
要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式安全的发布
a.在静态初始化函数中初始化一个对象引用
b.将对象的引用保存到volatile类型的域或者AtomicRererance对象中
c.将对象的引用保存到某个正确构造对象的final域中
d.将对象的引用保存到一个由锁保护的域中
线程安全的容器类提供了以下安全发布的保证
a.通过将一个键或者值放入一个hashTable、synchronizedMap或者ConcurrentMap中,可以安全的将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)
b.通过将一个元素放入到Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、SynchronizedList或者SynchronizedSet中,可以将元素发布到任何从这些容器中访问它的线程
c.通过将元素放到BlockingQueue或者ConcurrentLinkedQueue中,可以将元素安全的发布到任何从这些队列中访问该元素的线程
d.通过静态的初始化器初始化的任何对象都被安全发布
3.5.4事实不可变对象
如果对象从技术上来说是可变的,但其状态在发布后不会发生变化,把这种对象称为事实不可变对象
在没有额外的同步的情况下,任何线程都可以安全的使用被安全发布的事实不可变对象
3.5.5可变对象
对象的发布需求取决于它的可变性
不可变对象可以通过任意机制来发布
事实不可变对象必须通过安全方式来发布
可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来
3.6安全的共享对象
当发布一个对象时,必须明确对象访问方式
在并发程序中使用和共享对象时,可以使用一些实用的策略
线程封闭:对象只能由一个线程拥有,对象被封闭在线程中,并且只能由这个线程修改
只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程不能修改它。共享的只读变量包括不可变对象和事实不可变对象
线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步同步
保护对象:被保护的对象只能通过持有特定的锁来访问,保护对象包括封装在其他线程安全对象中的对象,以及已发布并且由某个特定锁保护的对象