一、锁的状态
在Hot Spot JVM实现中,锁有个专门的名字:对象的监视器Object Monitor
Monitor,描述为一种同步机制,它通常被描述为一个对象,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,当一个 monitor 被某个线程持有后,它便处于锁定状态。
jvm基于进入和退出Monitor对象来实现方法同步和代码块同步。
代码块的同步:
使用的monitorenter和monitorexit指令尝试获取monitor的所有权
1.线程试图获取对象锁对应的 monitor 持有权, monitor的进入计数器为 0,线程成功取得monitor后,将计数器值设置为1,取锁成功
2.如果线程已经占有该monitor,只是重新进入(可重入锁),则进入monitor的进入数加1
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
方法同步:
方法级的同步是隐式,即无需通过字节码指令来控制,它实现在方法的调用和返回之中
当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果被设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在此期间,其他任何线程都无法再获得此monitor。
在JVM中,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header
)、实例数据(Instance Data
)和对齐填充(Padding
)。
对象头主要包括两部分数据:Mark Word(标记字段) 和 Class Pointer(类型指针)。
Mark Word:用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键
Class Pointer:是对象指向它的类元数据的指针,虚拟机通过它来确定这个对象是哪个类的实例
锁的状态由低到高:无锁状态、偏向锁、轻量级锁状态、重量级锁状态
锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。
偏向锁:Mark Word存储的是偏向的线程ID
轻量级锁:Mark Word存储的是指向栈中锁记录的指针
重量级锁:Mark Word为指向堆中的monitor对象的指针
四种状态转换
1.每个线程在准备获取共享资源时,第一步,检查Mark Word里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁” 。
2.第二步,如果Mark Word不是自己的ThreadId,就会尝试使用CAS来替换Mark Word里面的线程ID为新线程的ID,成功,仍然为偏向锁,失败,升为轻量级锁,会按照轻量级锁的方式来竞争锁。
3.CAS将锁的Mark Word替换为指向锁记录的指针,成功,则为轻量级锁,失败的则进入自旋 。
4.自旋的线程在自旋过程中,成功获得资源(即之前获得资源的线程执行完成并释放了共享资源),则整个状态依然处于轻量级锁的状态,如果自旋失败,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己。
二、常用的锁
1.原子性(java.util.concurrent.atomic 包,Synchronized)
即一个或者多个线程访问,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
2.可见性(Volatile,Synchronized)
保证共享变量的修改能够及时可见(在工作内存中,每次修改共享变量后立刻同步回主内存中)
3.有序性(Synchronized,Lock,Volatile)
即程序执行的顺序按照代码的先后顺序执行
①Synchronized
可以使用在变量、方法、和类级别,可能会造成线程的阻塞
1.当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this)
2.当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁
3.当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例
synchronized是一种对象锁(锁的是对象而非引用变量),可以用来实现对临界资源的同步互斥访问 ,可重入锁。其可重入最大的作用是避免死锁
在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。
②Lock
Lock.lock():获取锁,如被锁定,则等待。
Lock.tryLock() :如未被锁定才能获取,有返回值。
Lock.tryLock(long timeout, TimeUnit unit) :获取锁,如被锁定,则最多等待timeout时间后返回获取锁状态。
Lock.lockInterruptibly() :在等待中若收到中断请求,会立即响应并抛出异常,避免无效等待。
unLock():释放锁,必须主动去释放锁,在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此需要在finally块中释放锁。
Lock代表实现类是ReentrantLock(可重入锁)
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。
ReadWriteLock代表实现类是ReentrantReadWriteLock
ReadWriteLock 维护了一对相关的锁,一个用于只读操作(readLock()),另一个用于写入操作(writeLock())
共享锁是指该锁可被多个线程所持有。如果线程对数据加上共享锁后, 获得共享锁的线程只能读数据,不能修改数据。
只要没有 writer,读取锁可以由多个 reader 线程同时保持,而写入锁是独占的
③volatile
仅能使用在变量级别,不会造成线程的阻塞。
内存可见,会使得所有对volatile变量的读写都直接刷到主存,即保证了变量的可见性。