java并发编程实战:第三章----对象的共享
我们不仅仅希望防止某个线程使用某个状态时,另一个线程在修改它;我们还希望某个线程修改了某个状态后,其他线程能够看到状态的变化。
一、可见性
重排序:在没有同步的情况下,编译器、处理器可能对代码的执行顺序进行一些调整
例如如下代码,由于没有使用同步机制,读线程可能看不见ready的修改,而一直循环下去;也可能由于重排序,看到了ready的修改number仍没修改而输出0
1、失效数据
在缺少同步的程序中产生错误的结果的一种情况。造成程序的不确定性。
2、非原子的64位操作
即使是失效数据也是程序过去运行中产生的数据。
但执行非原子的64位操作,JVM会分解为两个32位操作,从而可能造成错误的值。使用volatile关键字解决
3、加锁与可见性
加锁的作用不仅仅局限在互斥,还包括内存可见性。
锁可以确保某个线程以一种可以预测的方式来查看另一个线程的执行结果。即前一线程结果后一线程可见。
4、volatile变量
比synchronized更轻量级的同步机制,编译器和运行时都会注意到这个变量是共享的,在该变量上的操作和其他内存操作不会放在一起做重排序。
读取volatile变量的开销很小。
在开发阶段最好时候server模式,因为server模式会做更多的优化,例如重排序等可能导致并发问题
加锁机制能保证可见性和原子性,volatile只能保证可见性,使用条件:
- 写入不依赖当前值
- 访问时不用加锁
二、发布与逸出
发布:使对象能够在当前作用于外的代码使用,例子:
逸出:不该发布的对象被发布
不要在构造方法中使this的引用溢出,会发布一个不完整的对象
三、线程封闭
当访问共享的可变数据时,通常需要同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术称为线程封闭
需确保封闭在线程中的对象不会溢出
1、Ad-hoc线程封闭:完全有程序实现线程封闭,少用
2、栈封闭:只能通过局部变量访问对象,因为其他线程无法访问执行线程的栈区域
3、TheadLocal类ThreadLocal<T> -> Map<Thread, T>
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。
详见:(转)http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/
四、不变性
满足同步需求的另外一种方法是使用不可变对象。不可变对象一定是线程安全的
不可变对象:对象创建出来后无论如何对此对象进行非暴力操作(不用反射),此对象的状态(实例域的值)都不会发生变化,那么此对象就是不可变的,相应类就是不可变类,跟是否用 final 修饰没关系。(例:final域中保存了可变对象的引用)
条件:对象创建后状态不更改;域均用final修饰;正确创建(构造函数没有this溢出)
1、final域
2、通过volatile发布不可修改对象实现线程安全
因为cache是不可修改的,所以可以保证每个i和factors是对应的,并用volatile保证了可见性
五、安全发布
1、不正确的发布,正确对象被破坏
未被创建完成的正确对象被发布,其他线程可能看到一个失效的值或空指针
2、不可变对象与初始化安全性
任何线程都可以在不需要同步的情况下访问不可变对象,即使发布不可变对象时没有同步
3、安全发布的常用模式
可变对象通过安全的方式发布,即发布和使用他的线程必须进行同步
发布静态对象时,使用静态初始化器,由JVM在累得初始化时执行,并且存在同步机制
将对象的引用保存到volatile类型或AtomicReferance对象中或正确构造的final中
将对象发布到有锁保护的域中(HashTable,synchronizedMap,ConcurrentMap,Vector,CopyOnWriteArrayList,CopyOnWriteArraySet,synchronizedList,synchronizedSet,BlockingQueue,ConcurrentLinkedQueue)
4、事实不可变对象:技术上可变,实际不会变
任何线程都可以在不需要同步的情况下访问安全发布的事实不可变对象
5、可变对象
需要安全发布,并且访问需要同步机制
6、安全的共享对象