volatile
1. volatile修饰的变量具有可见性volatile是变量修饰符,其修饰的变量具有可见性,当对volatile标记的变量进行修改时,会将其他缓存中存储的修改前的变量清除,当用到缓存中的变量时,需要重内存中重新读取。
在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量相当于直接读写主存。
class MyThread extends Thread {
private volatile boolean isStop = false;
public void run() {
while (!isStop) {
System.out.println("do something");
}
}
public void setStop() {
isStop = true;
}
}
若有多个线程调用run方法,如果不用volatile修饰变量isStop,某个线程调用了setStop方法后,线程可能仍从原来寄存器或CPU缓存中取数,并没有立刻停止,这里的isStop使用了volatile修饰,使用setStop后会isStop值发生变化,会清空寄存器和CUP缓存中的值,其他线程再次取值时,会从主存中取得最新值,线程就立刻停止了。用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。2. volatile禁止指令重排
volatile可以禁止进行指令重排。
指令重排是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
public class SingletonExample5 {
// 私有构造函数
private SingletonExample5() {
}
// 1.memory = allocate 分配对象的内存空间
// 2.ctorInstance() 初始化对象
// 3.instance = memory 设置instance指向刚分配的内存
// JVM和CPU优化,发生了指令重排
// 1.memory = allocate 分配对象的内存空间
// 3.instance = memory 设置instance指向刚分配的内存
// 2.ctorInstance() 初始化对象
// 单例对象 volatile + 双重检测机制 ——> 禁止指令重排
public static volatile SingletonExample5 instance = null;
// 静态工厂方法
public static SingletonExample5 getInstance() {
if (instance == null) { // 双重检测机制
synchronized (SingletonExample5.class) { //同步锁
if (instance == null) {
instance = new SingletonExample5();
}
}
}
return instance;
}
}
如果不使用volatile修饰SingletonExample5的话,这个单例模式不是线程安全的。instance = new SingletonExample5()时,指令执行顺序是1.2.3,发生指令重排后变为1.3.2。线程1执行到instance = new SingletonExample5(),线程2执行到第一个if,此时线程执行到指令3,但是指令2的初始化并没有完成,线程2会直接获得实例,但实例并没有初始化完成,此时调用是有问题的。
synchronized
synchronized可作用于一段代码或方法,既可以保证可见性,又能够保证原子性。
可见性体现在:通过synchronized或者Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中。
原子性表现在:要么不执行,要么执行到底。
作用范围:
修饰一个代码块: 大括号括起来的代码,作用于调用的对象
修饰一个方法:整个方法,作用于调用对象
修饰静态方法:整个静态方法,作用于类的所有对象
修饰类:括号括起来的部分,作用于类的所有对象
public class SynchronizedExample1 {
// 修饰一个代码块: 大括号括起来的代码,作用于调用的对象
public void test1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
log.info("test1 - {}", i);
}
}
}
// 修饰一个方法:整个方法,作用于调用对象
public synchronized void test2() {
for (int i = 0; i < 10; i++) {
log.info("test2 - {}", i);
}
}
// 修饰静态方法:整个静态方法,作用于所有对象
public static synchronized void test3(int k) {
for (int i = 0; i < 10; i++) {
log.info("test3 - {} - {}", k, i);
}
}
// 修饰类:括号括起来的部分,作用于所有对象
public void test4(int k) {
synchronized (SynchronizedExample1.class) {
for (int i = 0; i < 10; i++) {
log.info("test4 - {} - {}", k, i);
}
}
}
}
总结
(1)从而我们可以看出volatile虽然具有可见性但是并不能保证原子性。若线程1和线程2执行count++时,都从主存中取得最新值为2,线程1执行+1操作后,写回主存,值为3,而线程2也执行+1操作后,写回主存,值为3,我们的预期值应该为4,为了是其线程安全,必须要给count++操作加上锁。
(2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。