volatile关键字与synchronized关键字
volatile关键字和synchronized
volatile作用
保证线程可见性(MESI 缓存一致性协议)
禁止指令重排序(CPU)
volatile应用
单例模式
(饿汉式)单例模式可以保证线程安全只创建一次。
public class DCL {
private static DCL dcl = new DCL();
private DCL(){}
public DCL getInstance(){
return dcl;
}
}
有些时候饿汉式单例模式,会在线程还没有开始调用这个对象的时候就已经创建了。所有为了解决这个问题引出了懒汉式单例模式。
public class DCL {
private static DCL dcl;
private DCL(){}
public DCL getInstance(){
if (dcl == null){
dcl = new DCL();
}
return dcl;
}
}
但是这种实现会产生一个问题线程不安全在刚刚初始化程序的时候恰巧有几个线程同时获取到了dcl为 null的情况,那么他们会创建很多个DCL对象,与单例模式相违背了,于是引出了下面的写法:
public class DCL {
private static DCL dcl;
private DCL(){}
public synchronized DCL getInstance(){
if (dcl == null){
dcl = new DCL();
}
return dcl;
}
}
上面的实现虽然解决了创建线程只有一个,也实现了懒加载,但是 synchroized是重量级锁,如果这个类中包含很多业务逻辑那么会特别影响性能,于是我们采用锁细化:
public class DCL {
private static DCL dcl;
private DCL(){}
public DCL getInstance(){
if (dcl == null){
synchronized(DCL.class){
dcl = new DCL();
}
}
return dcl;
}
}
上面程序解决了锁细化的问题,但是产生了新的问题,同样在多线程时会产生多个对象,于是我们在此基础上进行改良:
public class DCL {
private volatile static DCL dcl;
private DCL(){}
public DCL getInstance(){
if (dcl == null){
synchronized(DCL.class){
if (dcl==null) {
dcl = new DCL();
}
}
}
return dcl;
}
}
我们进行两次判定也叫双重检核,并且我们在DCL静态对象上加上了 volatile关键字,这样在第一次判定时会在对象初始化后避免多个程序再次进行锁定,毕竟锁是非常影响性能的。第二次判定为如果再多线程都加载到dcl为 null 时,将调用锁,之后获取到锁的第一个线程才能初始化对象,之后的线程将再次判断 dcl 是否为空,因为第一个线程已经初始化了对象,所以直接将初始化的对象返回了。但是在程序在编译器编译之后,指令会被分成三步 1给指令申请内存。2给成员初始化。3是把这块内存赋值给 dcl 当第二个线程过来的时候第一个判定条件就会发现该值已经有了,就不会进入到锁的判定,这样的返回值就是错误的。于是我们使用 volatile关键字 来禁止指令重新排序。这样在初始化的时候就能保证创建线程时,就算是多个线程同时加载也不会乱,并且会走到竞争锁的地方。
volatile关键字不能保证原子性
public class T {
volatile int count = 0;
void m(){
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m,"Thread"+i));
}
threads.forEach(Thread::start);
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
执行结果:
程序执行之后并没有使count的值变成100000。所以volatile不可以代替synchronized。