volatile
1、作用
(1)修饰的共享变量;
(2)保证可见性,变量都是从主存进行操作;
(3)部分保证顺序性,禁止指令重排序,建立内存屏障
2、注意事项:不保证原子性
3、结论
如果一个变量的操作,不会分解为多条指令,就是线程安全的;否则就是线程不安全的
(1)依赖某个共享变量的值进行修改操作,就会分解
(2)n++,n–,++n,–n,!flag,new对象是非线程安全的
单例模式
1、单例:在某些场景下,需要获取同一个对象
2、类加载在JVM内部执行,保证了线程间同步互斥的:
(1)只执行一次
(2)线程安全
3、指令重排序:
(1)JVM编译java文件为class字节码文件时、CPU执行机器码(JVM运行时解释字节码为字节码)时,可以进行指令重排序
(2)是否可以进行重排序,依赖指令前后依赖关系
int i = 9;
int j = i;//存在依赖关系,不能重排序
int m = 9;//不存在依赖关系,可以重排序
1、饿汉模式
代码:
public class SingleTest {
//饿汉模式:在类加载的时候就创建对象
public static SingleTest SINGLE = new SingleTest();
public SingleTest() {}
public static SingleTest getInstance(){
return SINGLE;
}
}
2、懒汉模式
(1)线程不安全的版本
public class SingleTest{
//懒汉模式
//存在线程安全问题
public static SingleTest SINGLE = null;
public SingleTest() {}
public static SingleTest getInstance() {
if(SINGLE == null){
SINGLE = new SingleTest();
/*
SINGLEA = new SingleTest()可以分解为:
(1)分配对象的内存空间
(2)初始化对象
(3)对象赋值给变量
不具有原子性,可见性
*/
}
return SINGLE;
}
}
(2)线程安全,但效率低的版本
public class SingleTest{
//懒汉式多线程安全版:存在效率低的问题
public static SingleTest SINGLE;
public SingleTest() {}
//对方法加锁
public synchronized static SingleTest getInstance() {
if (SINGLE == null) {
SINGLE = new Sington();
}
return SINGLE;
}
}
(3)线程安全,且效率高
<1>没有使用volatile,此时只能保证第一个线程的安全性,不能保证后面线程的安全性
public class SingleTest{
//懒汉式多线程安全版,且效率高
public static SingleTest SINGLE;
public SingleTest() {}
//对方法加锁,二次判断,性能高
public synchronized static SingleTest getInstance() {
if (SINGLE == null) {
synchronized (Sington.class) {
if (SINGLE == null) {
SINGLE = new SingleTest();
//(1)分配对象的内存空间
//(2)初始化对象
//(3)对象赋值给变量
}
}
}
return SINGLE;
}
}
SINGLE = new SingleTest();可以分解为(1)分配对象的内存空间;(2)初始化对象;(3)对象赋值给变量;
在第1个线程执行时,(3)可以重排序到(2)之前;此时第二个线程在执行时,SINGLE有指向的对象,但是对象没有初始化,就会出错
<2>使用volatile关键字,可以保证所有线程的安全性
public class VolatileSingle {
//双重校验锁的单例模式
public volatile static VolatileTest SAFESINGTON;
//volatile的使用保证了变量使用前后的顺序性
public static VolatileTest getInstance(){
//多个线程进入代码块,按序执行
if(SAFESINGTON == null){
synchronized (VolatileTest.class){
//第一个进入的线程初始化对象,后序进入的直接退出
//后续的线程并发的执行getInstance(),不会进入if代码,直接返回已实例的对象
//效率非常高
if(SAFESINGTON == null){
SAFESINGTON = new VolatileTest();
/*
SAFESINGTON = new VolatileTest()分解为
(1)分配对象的内存空间
(2)初始化对象
(3)对象赋值给变量
*/
}
}
}
return SAFESINGTON;
}
}
对于重排序:
1、 一个线程看自己内部的代码,都是有序的:虽然还是会重排序,但是前后依赖是可以保证的,可以得到期望的结果
2、从某个线程的角度,看其他线程内代码执行的顺序,都是无序的