可见性
一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)。
Java内存模型是通过将在工作内存中的变量修改后的值同步到主内存,在读取变量前从主内存刷新最新值到工作内存中,这种依赖主内存的方式来实现可见性的。
无论是普通变量还是volatile变量都是如此,
volatile
区别在于:volatile 的特殊规则保证了 volatile 变量值修改后的新值立刻同步到主内存,
每次使用 volatile 变量前立即从主内存中刷新,因此 volatile 保证了多线程之间的操作变量的可见性,而普通变量则不能保证这一点。
除了 volatile 关键字能实现可见性之外,还有 synchronized, Lock,final 也是可以的。
synchronized
使用 synchronized 关键字,在同步方法/同步块开始时(Monitor Enter),
使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),
在同步方法/同步块结束时(Monitor Exit),会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。
Lock
使用Lock接口的最常用的实现ReentrantLock(重入锁)来实现可见性:
当我们在方法的开始位置执行 lock.lock() 方法,这和 synchronized 开始位置(Monitor Enter)有相同的语义,
即使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),
在方法的最后 finally 块里执行 lock.unlock() 方法,
和 synchronized 结束位置(Monitor Exit)有相同的语义,即会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。
final
final 关键字的可见性是指:
被final修饰的变量,在构造函数数一旦初始化完成,
并且在构造函数中并没有把“this”的引用传递出去(“this”引用逃逸是很危险的,其他的线程很可能通过该引用访问到只“初始化一半”的对象),
那么其他线程就可以看到final变量的值。
有序性
对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。
这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。
用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。
Java提供了两个关键字 volatile 和 synchronized 来保证多线程之间操作的有序性,
volatile 关键字本身通过加入内存屏障来禁止指令的重排序,
而synchronized关键字通过一个变量在同一时间只允许有一个线程对其进行加锁的规则来实现,
在单线程程序中,不会发生“指令重排”和“工作内存和主内存同步延迟”现象,只在多线程程序中出现。 |