内存模型
内存模型.png在Java内存模型中,线程工作在自己的工作内存,他会保留主存的变量拷贝。对于普通变量,为了保证执行效率,在工作内存中对变量的改变并不会立刻刷新到主存中中。
Volatile关键字
volatile的意思是易变的,不需要被优化的。当一个变量被加上了这个关键字,就表示这个变量不需要被优化、缓存。对该变量的修改会被立刻刷新到主存中,而当其他线程读取该变量时,也会去主存中读取新值。
使用场景
1.修饰线程中断标志位
volatile boolean isCancel;
当我们希望通过标志位去结束一个正在运行的线程,那么应该对该标志修饰volatile。
2.单例模式
public class Singleton {
private volatile static Singleton sInstance; //保证对象的可见性
private Singleton() {
}
public static Singleton getsInstance() {
if (sInstance == null) { //减少每次创建对象双重锁的开销
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
问:为什么这里的volatile关键字是必要的?
答:因为创建对象不是一个原子操作。
禁止指令重排
假设创建一个对象需要3个步骤:
(1)分配内存空间。
(2)初始化对象。
(3)将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
(1)分配内存空间。
(2)将内存空间的地址赋值给对应的引用。
(3)初始化对象
如果采用后面的流程,可能导致引用不为null,但是对象还没有初始化出来,其他的线程进行非空判断后,会引用这个对象,导致错误。
volatile的原理和机制
下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1.它确定指令重新排序时不会把其后的指令排在屏障之前,也不会把前面的指令排到屏障之后。即执行到屏障指令时,其前面的指令已经全部执行完,且该屏障指令的执行结果对后面的指令可见。
2.它会强制对缓存的修改操作立刻写入主存
3.如果是读取操作,它会导致其他线程中的缓存变量无效
synchronized关键字
要理解这个关键字的作用,就必须明白Java线程安全中的一个重要概念---原子性。
在Java中,对基本数据类型变量的读取和赋值操作是原子性的。
如果要实现更大范围的原子性操作,就必须用synchronized和Lock来实现,他们能够保证任一时刻只有一个线程执行该代码块,从而保证了原子性。
当访问临界资源时,为了避免数据不一致,我们会采取同步操作,以确保在某一时刻只有一个线程在操作资源。
synchronized的用法
-
对象锁
修饰对象方法或者明确指定某一个对象 -
类锁
修饰静态方法或者锁定.class
同一把锁在某一时刻只能被一个线程持有,当另一个线程尝试着获取该锁的时候,会陷入阻塞状态。JVM维持了一个等待该锁的队列,一旦该锁被某个线程释放了,那么这个等待队列中的线程会重新进入Ready(可运行)状态,等待调度器的下一次分配,进而去获取需要的锁。
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是类,该类所有的对象用同一把锁。
网友评论