synchronized和volatile的区别

讨论两者前,先要了解JMM(java memory model)java内存模型:每个线程都会有自己的工作内存,所有线程的工作内存共同指向主内存。
如果一个变量,在多个线程的内存中,都存在副本,那么这个变量就是这些线程的共享变量。
如果一个线程对共享变量进行修改,能及时的被其他线程获取,则该共享变量为线程可见。
对A、B两个线程实现线程可见性,假如在A线程修改了数据,则需要及时把A线程的工作内存中修改的数据刷新到主线程,接着及时把主线程中的数据刷新到B线程的工作内存中。
有以上情况,是由于每个线程对共享变量的操作,只能在自己的工作内存中进行,不能直接操作主内存。且不同线程间无法互相调用对方工作内存中的变量,只能通过主内存间接调用。

现区分两者。
synchronized:
1、需要加锁,运行更复杂,更消耗性能,会导致线程阻塞。
2、锁一致的情况下,线程解锁前,必须把当前线程的工作内存中的共享变量刷新到主内存中;线程加锁时,必须清空当前线程的工作内存中的共享变量的值,待使用该共享变量时,重新从主内存中读取。
3、既可以保证线程间可见性,又能保证变量操作原子性。

volatile:
1、不需要加锁,更轻量级,不会出现线程阻塞。
2、对volatile修饰的变量执行写操作,会在写操作后,先改变线程内工作内存的共享变量的值,再刷新到主内存中;而执行读操作,会先获取主内存中最新的变量值再读取到当前线程的工作内存中。
3、只可以保证线程间可见性,不能保证变量操作原子性。

针对单例设计模式中的volatile,做如下说明:

原本的单例设计模式的懒汉式:

class SingleTon {
    private SingleTon(){}
    private static SingleTon singleTon;
    public static SingleTon getSingleTon() {
        if (singleTon == null) {
            singleTon = new SingleTon(); //6
        }
        return singleTon;
    }
}

这么做,在多线程中,如果在将要执行第六行时,本线程执行权被其他线程抢走,则其他线程也会判断singleTon == null,会导致两个线程都进入第六行执行,产生了两个实例。
所以初步可以在单例设计中对外提供的公共访问方法上加上sychronized,如下:

class SingleTon {
    private SingleTon(){}
    private static SingleTon singleTon;
    public static SingleTon getSingleTon() {
        synchronized (TestDemo.class) {
            if (singleTon == null)
                singleTon = new SingleTon();
        }
        return singleTon;
    }
}

但这样做,无论SingleTon是否为空,都会执行加锁操作,问题是SingleTon不为空的时候,无需创建单例,加锁平白消耗了性能。所以进行如下改进:

class SingleTon {
    private SingleTon(){}
    private static SingleTon singleTon;
    public static SingleTon getSingleTon() {
        if (singleTon == null) {
            synchronized (TestDemo.class) {
                if (singleTon == null)
                    singleTon = new SingleTon(); //8
            }
        }
        return singleTon;
    }
}

这里就是采用了双重判断锁机制(double-checked locking):在加锁之前,先判断单例是否已经有了实现的实体对象,如果有了直接就跳过加锁,进行返回,没有的话才进行加锁。
而在锁内部进行第二重判断的目的是,如果没有内部第二重非空判断,初始创建单例对象时,多条线程可以同时穿过第一层非空判断到达锁前,一个线程执行完毕得到对象然后释放锁之后,由于剩余线程已经穿过非空判断,则会继续按照获取锁释放锁有序创建多个对象,导致单例失败。
但这样还是有问题,这个问题出在代码的重排序上,重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。反观第八行执行singleTon = new SingleTon();创建实例对象,其实包含三个步骤:
(1)对SingleTon对象先分配内存空间。
(2)调用SingleTon类的构造方法,初始化成员变量,形成实例对象。
(3)将SingleTon实例对象,指向刚刚创建的内存空间的地址。(到此SingleTon实例对象非null)
这中间,(2)和(3)是没有依赖关系的,所以可能会被重排序,执行顺序修改为(1)(3)(2),这个重排序不会影响到最终结果,这样,会出现另一个线程读取singleTon,发现已经有了实例对象,所以直接访问该对象并准备返回,却又发现该对象还未利用构造方法初始化、或者出现初始化对象不完全的现象,从而出现bug。
因此在singleTon变量前加入volatile关键字,利用的就是该关键字禁止代码重排序的特性。最终如下:

public class TestDemo {
    public static void main(String[] args) {
        SingleTon s1 = SingleTon.getSingleTon();
        SingleTon s2 = SingleTon.getSingleTon();
        System.out.println(s1);
        System.out.println(s2);
    }
}
class SingleTon {
    private SingleTon(){}
    private static volatile SingleTon singleTon;
    public static SingleTon getSingleTon() {
        if (singleTon == null) {
            synchronized (TestDemo.class) {
                if (singleTon == null) {
                    singleTon = new SingleTon();
            }
        }
        return singleTon;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值