sychronized关键字,JMM内存模型,以及线程安全三大特性

我们先说背景,线程安全是多线程编程的关注重点,导致线程不安全的关键点有两个:
一是存在线程间共享的变量;
二是存在多个线程同时操作同一共享变量的情况。

为了保证线程安全,我们必须保证同一时间,只有一个线程可以拿到共享变量,其他线程必须等待该线程处理完成之后,再获取共享变量做处理。

在实际Java开发中,我们是通过给方法或者代码块加sychronized关键字来保证线程安全的。
需要注意的是,必须当 一段代码满足了原子性,可见性和有序性,我们才认为它是线程安全的,具体这三个特性是什么,咱们后续会说。

sychronized关键字,通常有三个使用方式:

1.在实例方法上加sychronized,给当前对象加锁,在执行该方法前,需要先获取到当前对象锁;
2.在静态方法上加sychronized,给当前类加锁,在执行该方法前,需先获取到当前类的锁;
3.在代码块上加sychronized,如果是静态代码块,则需要先获取当前类的锁,如果是实例方法代码块,则需要先获取当前对象的锁。

sychronized的底层语义原理:

通过判断内存对象的对象头中锁信息,通过管程对象指令来实现同步。

同步这个功能在Java虚拟机中实际上是通过管程对象来实现的。
在Java虚拟机中,对象在内存中布局分为三块:对象头,实例变量和填充数据。
实例变量:用来存储本类以及父类的属性数据,

填充数据:因为虚拟机要求对象的起始地址必须是8字节的整数倍,所以有时候需要填充数据来保证字节对齐

对象头:对象头中有一个MarkWord字段,用来存储对象的hashCode,锁信息,年龄和gc标志等信息,JVM就是通过判断对象头中MarkWord字段的锁信息,然后通过管程对象Monitor的enter(获取锁,进入加锁代码)和exit(加锁代码执行结束,释放锁)指令来控制代码执行,从而实现同步。

在这里插入图片描述


JMM(Java Memory Model)Java内存模型:

为什么要有内存模型?

因为CPU的处理速度和内存的读写速度不是一个量级上的,为了平衡这个速度的差异,每个CPU实际上都是做了缓存的。
在这里插入图片描述
这个缓存在单线程的时候没有问题,因为CPU的缓存只被一个线程访问,所以不会出现访问冲突。

单核CPU,多线程的情况下也是没有问题的,因为只有一个CPU,哪怕线程是不断切换执行的,那么同一时刻也是只有一个线程能够获取缓存。

但是在多核CPU和多线程的情况下,就可能有问题了,因为多个CPU可能在同一时间内各自执行多个线程,而如果这些线程又对内存中的同一数据进行处理,但是因为CPU缓存机制,那么各个线程访问的缓存数据就有可能不一致。

比如A,B两个线程同时操作内存中的共享变量int X=0,
然后A线程把X赋值为2,B线程把X-1,但是实际上因为A,B两个线程由不同的CPU执行,所以他们访问X的缓存其实是不一样的。

1.这样就可能导致 A,B一起开始执行,然后从内存中读取到X=0,然后加载到缓存;
2.A把缓存X设为了2,然后把缓存里的X=2写到内存里。
3.B又把缓存X的值设为了-1,然后把X=-1写到了内存里。

那这时候其实就违背了 原子性(A的操作执行了,但是实际上是没生效的)和可见性(A的操作结果,对于B来说,是无法实时获取的)

如何保证原子性?
我们可以通过sychronized关键字来保证原子性,sychronized底层实际上是通过monitorenter和monitorexit这两个字节码指令来保证的。

如何保证可见性?
我们可以通过volatile关键字来保证可见性,被volatile修饰的变量,每次被修改后都会立即同步到主内存,并且在背刺读取时,都是直接从主内存刷新到缓存,避免了缓存数据更新不及时可能带来的问题

如何保证有序性?
有序性问题其实是因为多线程环境下程序编译生成的机器码之类可能会指令重排,我们可以通过volatile来禁止指令重排,也可以通过sychronized来保证同一代码统一时刻只有一个线程访问,来避免有序性问题。

demo:双重校验锁实现的单例模式

public class DoubleCheckLock{
    //通过volatile来保证变量的可见性
    private static volatile singleTon;
    
    private DoubleCheckLock(){}
    
    public static DoubleCheckLock getInstance(){
        //如果singleTon已经创建,则无需走下面逻辑
        if(singleTon!=null){
            //通过sychronized来给这段代码加锁
            sychronized(DoubleCheckLock.class){
                //双重检测,同时只允许一个线程做该判断以及执行创建对象操作
                //防止多线程情况下有两个线程同时进来,都判断刚刚那个singleTon!=null,从而创建多个对象
                if(singleTon!=null){
                    singleTon = new DoubleCheckLock();
                }
            }
        }
        return singleTon;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值