同步
同步不仅可以阻止一个线程看到对象处于不一致的状态之中,还可以保证进入同步方法或同步代码块的每个线程,都看到由同一个锁保护的之前的所有的修改效果。
原子性
读或写一个非long
或double
的基本类型变量是原子的。
语言规范保证了线程在读取原子数据的时候,不会看到任意的数值,但它并不保证一个线程写入的值对于另一个线程是可见的。
为了线程之间进行可靠的通信,也为了互斥访问,同步是必要的。
内存模型
一个线程所做的变化何时以及如何变成对其他线程可见。
如果对共享的可变数据的访问不能同步,其后果将非常可怕,即使这个变量是原子可读写的。
Thread.stop
方法
这个方法不提倡使用,它本质上是不安全的——使用它会导致数据遭到破坏。
建议让一个线程轮询一个boolean
域,这个域一开始为false
,但是可以通过其他线程设置为true
,以表示一个线程将终止自己。
有问题的同步
以下程序将永远不会终止,因为后台线程永远在循环。
问题在于:由于没有同步,就不能保证后台线程何时“看到”主线程对
stopRequested
的值所做的改变。没有同步,虚拟机将如下代码:
转变为
这是可以接受的,这种优化也称为提升。结果却是活性失败:这个程序无法前进。
使用synchronized
修正上述程序
注意,必须同步读和写操作。
同步方法只是为了通信效果,不是为了互斥访问。
使用volatile
修正上述程序
volatile
不执行互斥访问,但可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值。
volatile
注意事项
增量操作符(
+
)不是原子的,会执行两步操作:首先读取值,然后写回一个新值,相当于原来的值再加上1。安全性失败
使用synchronized
修复不当使用volatile
导致的bug
使用AtomicLong
修复不当使用volatile
导致的bug
不共享可变的数据
要么共享不可变的数据,要么压根不共享。
将可变数据限制在单个线程中。
采用这一策略,对它建立文档就很重要,以便它可以随着程序的发展而得到维护。
事实上不可变的对象
让一个线程在短时间内修改一个数据对象,然后与其他线程共享,这是可以接受的,只同步共享对象引用的动作。然后其他线程没有进一步的同步也可以读取对象,只要它没有再被修改。
安全发布:将事实上不可变的对象的引用从一个线程传递到其他的线程
安全发布对象引用的方法:将它保存在静态域中,作为类初始化的一部分;将它保存在volatile
域、final
域或通过正常锁定访问的域中;将它放到并发的集合中。
结论
当多个线程共享可变数据的时候,每个读或写数据的线程都必须执行同步。
如果没有同步,就无法保证一个线程所做的修改可以被另一个线程获知。
未能同步共享可变数据会造成程序的活性失败和安全性失败。
如果只需要线程之间的交互通信,而不需要互斥,volatile
修饰符是一种可以接受的同步形式。