java 中 synchronized 关键字
1、synchronized 关键字简介
synchronized 是 java 中的一个关键字,在中文中为同步,也被称之为'同步锁',以此来达到多线程并发访问时候的并发安全问题,可以用来修饰代码块、非静态方法。静态方法等;修饰代码块时:给当前指定的对象加锁修饰非静态方法时:作用于当前实例加锁修饰静态方法时:作用于当前类对象加锁 synchronized 在 java 内存模型中的主要作用原子性:通过 monitorenter 和 monitorexit 指令,保证被 synchronized 修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到可见性:保证共享变量的修改能够及时可见,对一个变量的 unlock 操作之前,必须把此变量同步回主内存中(store 和 write 操作)有序性:一个变量在同一时刻只允许一条线程对其执行 lock 操作,这条规则决定了持有同一个锁的两个同步块只能串行执行
2、synchronized 修饰代码块
当 synchronized 修饰代码块时,有以下几种情况
1、this 关键字
这里的 this 就是等价于调用这个方法的对象,synchronized 锁的就是 this 这个对象的锁,若有多个对象调用方法,各个对象锁之间相互独立,互不影响
2、Class.class
这里 synchronized 锁的对象为类锁,在需要类锁的代码不能同时执行,但是与非需要类锁的对象锁或者与没有加锁的代码可以同时执行用 synchronized 进行加锁时看获得锁是对象还是类的锁,还有的是 synchronized 锁住的是一个对象或者类(其实也是对象),而不是方法或者代码段。
3、synchronized 修饰实例方法
当 synchronized 修饰实例方法时,锁的是该类的实例对象
4、synchronized 修饰静态方法
由于 static 静态方法是属于类的而不属于对象的,所以 synchronized 修饰的静态方法锁定的是这个类的所有对象
5、synchronized 的底层实现原理
在 java 内存模型中,synchronized 可以保证原子性、有序性、可见性,在这之前,先谈谈对象在 HotSpot 虚拟机中的分布,主要有三部:对象头(Header)、实例数据(Instance Data)和对象填充(Padding)
对象头
对象头主要包括两部分信息,第一部分用于存储对象自身的运行时数据、如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,官方称之为'Mark Word',还有一部分称之为类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
存储内容标志位状态对象哈希码、对象分代年龄 01 未锁定指向锁记录的指针 00 轻量级锁定指向重量级锁的指针 10 膨胀(重量级锁定)空,不需要记录信息 11GC 标识偏向线程 ID、偏向时间戳、对象分代年龄 01 可偏向
实例数据
实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。这部分的存储顺序会受到虚拟机分配策略参数(-XX: FieldsAllocationStyle) 和字段在 Java 源码中定义顺序的影响。
对齐填充
对齐填充并不是必然存在的,也没有特别的含义,仅仅只是起着占位符的作用,由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,就是对象的大小必须是 8 字节的整数倍,而对象头部分正好是 8 字节的倍数(1 倍或者 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
原子性
synchronized 实现原子性底层是通过 JVM 来实现的,同一时间只能有一个线程去执行 synchronized 中的代码块;每一个对象都有一个监视器 monitor 来关联,监视器被占用时会被锁住,其他线程无法获取该 monitor,当 JVM 执行某个线程的的内部方法的 monitorenter,它会尝试去获取该对象的 monitor 的所有权,过程如下 1、若 monitor 的进入数为 0,线程可以进入 monitor,并将该 monitor 的进入数置为 1,那么该线程就成为 monitor 的所有者 2、若线程已拥有 monitor 的所有权,允许它重入 monitor,则进入 monitor 的进入数加 1(recursions:记录线程拥有锁的次数)3、若其他线程已经占有 monitor 的所有权,那么当前尝试获取 monitor 的所有权的线程会被阻塞,直到 monitor 的进入数变为 0,才能重新尝试获取 monitor 的所有权。monitorexit 指令 1、能执行 monitorexit 指令的线程一定是拥有当前对象的 monitor 的所有权的线程。2、当执行 monitorexit 时会将 monitor 的进入数减 1。当 monitor 的进入数减为 0 时,当前线程退出 monitor,不再拥有 monitor 的所有权,此时这个 monitor 阻塞的线程可以尝试去获取这个 monitor 的所有权。
可见性
synchronized 通过内存屏障保证可见性,同样的我们知道 volatile 是通过内存屏障来保证可见性的,1、monitorenter 指令之后,synchronized 内部的共享变量,每次读取数据的时候被强制从主内存读取最新的数据。2、monitorexit 指令也具有 Store 屏障的作用,也就是让 synchronized 代码块内的共享变量,如果数据有变更的,强制刷新回主内存。数据修改之后立即刷新回主内存,其他线程进入 synchronized 代码块后,使用共享变量的时候强制读取主内存的数据。
有序性
同样的,synchronized 也是通过 monitorenter、monitorexit 指令嵌入上面的内存屏障,既具有加锁、释放锁的功能,同时也具有内存屏障的功能
版权声明: 本文为 InfoQ 作者【jun】的原创文章。
原文链接:【http://xie.infoq.cn/article/716e0db478599c2726a1b5362】。文章转载请联系作者。
评论