一、原子性
所谓原子性既是指操作是单一不可分割的。举个例子:
(1)int i = 0;
(2)i = i + 1;
对语句(2)," i = i + 1" 就不是原子性操作。因为" i = i + 1" 实际上可以分解为3个操作:(1)读取变量 i 的当前值;(2)使当前值 i 和1做加法运算;(3)将加完后的值赋给 i 变量。
在多线程环境中,非原子操作可能会受其他线程的干扰。比如,上述例子如果没有对语句(2)的代码进行同步(Synchronization)处理,则可能出现在执行第2个操作的时候,拿到的 i 的变量的值已经被其他线程修改过了。如果想要避免这种多线程不安全的情况,那么久需要使用synchronized关键字进行修饰以实现原子性操作。
synchronized关键字可以实现操作的原子性的实质是:通过该关键字所包括的临界区(即指该关键字修饰的区域)的排他性保证在任何一个时刻只有一个线程能够执行临界区中的代码,这使得临界区中的代码代表了一个原子操作,语句(2)的三个操作此时可以看成是一个排他的原子操作。除此之外,synchronized关键字还起到另一个作用——保证内存的可见性(Memory Visibility),以下解释道。
二、内存可见性
在多核环境中,多进程任务的情况下,某个CPU执行某个线程的代码语句时,对变量的值的修改可能仅仅是被写入CPU缓存区,而没有写入主内存。由于每个CPU都有自己的缓存区,因此一个CPU缓存区中的内容对于其他CPU而言是不可见的。这就导致了在其他CPU上运行的其他线程可能无法“看到”该线程对某个变量值的更改,也就是说这些线程需要用到这个变量时,可能会去主内存获取,而这个值可能不是“最新的”。这就是所谓的内存可见性。
synchronized关键字的另一个作用就是保证了一个线程执行临界区中的代码时,所修改的变量值对于稍后执行该临界区的线程来说是可见的。这对于保证多线程代码的正确性来说非常重要。
要提到的一点是:
volatile关键字也能够保证内存可见性。即一个线程对一个采用volatile关键字修饰的变量的值的更改,对于其他访问该变量的线程而言总是可见的。也就是说能够保证其他线程读到的变量值一定是一个“最新的”的值。
注意:volatile关键字和synchronized关键字区别:
volatile关键字只能保证内存可见性,它并不能像synchronized关键字所代表的内部锁那样能够保证操作的原子性。volatile关键字实现内存可见性的核心机制是:当一个线程修改了一个volatile修饰的变量的值时,该值会被写入主内存(即RAM)而不仅仅是当前线程所在的CPU的缓存区,而其他CPU的缓存区中存储的该变量的值也会因此而失效(从而得以更新为主内存中该变量的“新值”)。这就保证了其他线程访问该volatile修饰的变量时,总是可以获取到该变量的最新值。
三、指令重排序
指令重排序是指编译器和CPU为了提高指令的执行效率可能会进行指令重排序,这使得代码的实际执行方式可能不是按照我们所认为的方式进行。
例如:
int a = 1; //语句(1)
int b = 2; //语句(2)
a = a + 3; //语句(3)
b = a*a; //语句(4)
看到以上代码,你可能会认为代码语句的执行一定是:
语句(1)=> 语句(2)=> 语句(3)=>语句(4)
其实不然,代码语句的执行可能会是以下顺序:
语句(2)=> 语句(1)=> 语句(3)=>语句(4)
而Volatile修饰了之后就可以保证指令按顺序执行。
禁止指令重排序虽然导致编译器和CPU无法对一些指令进行可能的优化,但是它可以避免线程不安全的问题。
Volatile、synchronized两者的区别及联系:
1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住(排他性,也是原子性的 体现)。
2.volatile只能用于修饰变量;而synchronized则可以使用在变量、方法、和类等。
3.volatile仅能实现变量的修改可见性,不能保证原子性(线程A修改了变量还没结束时,另外的线程B可以看到已修改的值,而且可以修改这个变量,而不用等待A释放锁,因为Volatile 变量没上锁);而 synchronized 则可以保证变量的修改可见性和原子性。
4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞和上下文切换。
5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
6.在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原子操作。当变量的值由自身决定时,如n=n+1、n++ 等,volatile关键字将失效。只有当变量的值和自身无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。
7.“锁是昂贵的”,谨慎使用锁机制。