Java锁的定义
锁的内存语义
- 锁可以让临界区互斥执行,还可以让释放锁的线程向同一个锁的线程发送消息;
- 锁的释放要遵循Happens-before原则(锁规则:解锁必然发生在随后的加锁之前);
- 锁在Java中的具体表现是Synchronized和Lock;
锁的释放
线程A释放锁后,会将其共享变更操作刷新到主内存中。
锁的获取
线程B获取锁时,JVM会将该线程的本地内存置为无效,被监视器保护的临界区代码必须从主内存中读取共享变量。
锁的释放和获取
- 线程A释放一个锁,实质是线程A告知下一个获取到该锁的某个线程其已变更该共享变量。
- 线程B释放一个锁,实质是线程B得到了线程A告知其(在释放锁之前)变更共享变量的消息。
- 线程A释放锁,随后线程B竞争得到该锁,实质是线程A通过主内存向线程B发消息告知其变更了该共享变量。
Synchronized的综述
- 同步机制:Synchronized是Java同步机制的一种实现,即互斥锁机制,它所获得的锁叫做互斥锁。
- 互斥锁:指的是每个对象的锁一次只能分配给一个线程,同一时间只能由一个线程占用
- 作用:Synchronized用于保证同一时刻只能由一个线程进入到临界区,同时保证共享变量的可见性、原子性和有序性。
- 使用:当一个线程试图访问同步代码方法(块)时,它首先必须得到锁,退出或抛出异常时必须释放锁。
Synchronized的使用
Synchronized实现原理
- 在JVM中,同步的实现是通过监视器锁的进入的退出实现的,要么显示通过monitorenter和monitorexit指令实现的,要么隐示的通过方法调用和返回指令实现。
- 对于Java代码来说,或许最常用的同步实现就是同步方法。其中同步代码块是通过使用monitorenter和monitorexit实现的,而同步方法却是使用ACC_SYNCHRONIZED标记符隐示的实现,原理是通过方法调用指令检查该方法在常量池中是否包含ACC_SYNCHRONIZED标记符。
同步代码块实现原理
monitor监视器
- 每个对象都有一个监视器,在同步代码块中,JVM通过monitorenter和monitorexit指令实现同步锁的获取和释放功能。
- 当一个线程获取同步锁时,即是通过获取monitor监视器而等价为获取到锁。
- monitor的实现类似于操作系统中的管程。
monitorenter指令
每个对象都有一个监视器。当该监视器被占用时即是锁定状态(或者说获取监视器即是获得同步锁)。线程执行monitorenter指令时会尝试获取监视器的所有权,过程如下:
- 若该监视器的进入次数为0,则该线程进入监视器并将进入次数设置为1,此时该线程即为该监视器的所有者。
- 若线程已占有该监视器并重入,则进入次数+1。
- 若其他线程已经占有该监视器,则线程会被阻塞直到监视器的进入次数为零,之后线程间会竞争获取该监视器的所有权。
- 只有首先获得锁的线程才能允许继续获取多个锁。
monitorexit指令
执行monitorexit指令将遵循以下步骤:
- 执行monitorexit指令的线程必须是对象实例所对应的监视器的所有者。
- 指令执行时,线程会先将进入次数-1,若-1之后进入次数变为0.则线程退出监视器(即释放锁)
- 其他阻塞在该监视器的线程可以重新竞争该监视器的所有权。
小结
- 在同步代码块中,JVM通过monitorenter和monitorexit指令实现同步锁的获取和释放功能。
- monitorenter指令是在编译后插入到同步代码块的开始位置。
- monitorexit指令是插入到方法结束处和异常处。
- JVM要保证每个monitorenter必须有对应的monitorexit与之配对。
- 任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。
- 线程执行monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获取对象的锁。
- 线程执行monitorexit指令时,将会将进入次数-1直到变成0释放监视器。
- 同一时刻只有一个线程能够成功,其他失败的线程会被阻塞,并放入到同步队列中,进入BLOCKED状态。
同步方法同步原理
- 区别在于同步代码块的监视器实现,同步方法通过 ACC_SYNCHRONIZED标记符隐示的实现。
- 原理是通过方法调用指令检查该方法在常量池中是否包含ACC_SYNCHRONIZED标记符,如果有,JVM要求线程在调用之前请求锁。