可重入锁(又名递归锁)
可重入锁是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
可重入锁种类
- 隐式锁Synchronized
synchronized是java中的关键字,默认是可重入锁,即隐式锁
在同步块中
java
public class ReEntryLockDemo {
public static void main(String[] args)
{
final Object objectLockA = new Object();
new Thread(() -> {
synchronized (objectLockA)
{
System.out.println("-----外层调用");
synchronized (objectLockA)
{
System.out.println("-----中层调用");
synchronized (objectLockA)
{
System.out.println("-----内层调用");
}
}
}
},"a").start();
}
}
//-----外层调用
//-----中层调用
//-----内层调用
在同步方法中
java
public class ReEntryLockDemo
{
public synchronized void m1()
{
//指的是可重复可递归调用的锁,在外层使用之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁
System.out.println(Thread.currentThread().getName()+"\t"+"-----come in m1");
m2();
System.out.println(Thread.currentThread().getName()+"\t-----end m1");
}
public synchronized void m2()
{
System.out.println("-----m2");
m3();
}
public synchronized void m3()
{
System.out.println("-----m3");
}
public static void main(String[] args)
{
ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
reEntryLockDemo.m1();
}
}
/**
* main -----come in m1
* -----m2
* -----m3
* main -----end m1
*/
Synchronized的重入实现机理
ObjectMoitor.hpp
cpp
140行
ObjectMonitor() {
_header = NULL;
_count = 0; //用来记录该线程获取锁的次数
_waiters = 0,
_recursions = 0;//锁的重入次数
_object = NULL;
_owner = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
_WaitSet = NULL; //存放处于wait状态的线程队列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;//存放处于等待锁block状态的线程队列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
-
ObjectMoitor.hpp底层:每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。_count _owner
-
首次加锁:当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
-
重入:在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
-
释放锁 :当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放
显式锁Lock
显式锁(即Lock)也有ReentrantLock这样的可重入锁
感觉所谓的显式隐式即是指显示/隐式的调用锁
注意:lock unlock要成对
java
public class ReEntryLockDemo {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
{
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "t1").start();
}
}
}
//t1 ----come in 外层调用
//t1 ------come in 内层调用
假如lock unlock不成对,单线程情况下问题不大,**但多线程下出问题
**
java
public class ReEntryLockDemo {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
} finally {
lock.unlock();
}
} finally {
//lock.unlock();//-------------------------不成对|多线程情况
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try
{
System.out.println("t2 ----外层调用lock");
}finally {
lock.unlock();
}
},"t2").start();
}
}
//t1 ----come in 外层调用
//t1 ------come in 内层调用
//(t2 ----外层调用lock 假如不成对,这句话就不显示了)
死锁及排查
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
a跟b两个资源互相请求对方的资源
死锁产生的原因
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
java
public class DeadLockDemo {
public static void main(String[] args) {
Object object1 = new Object();
Object object2 = new Object();
new Thread(()->{
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"\t 持有a锁,想获得b锁");
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//使得线程b也启动
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"\t 成功获得b锁");
}
}
},"A").start();
new Thread(()->{
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"\t 持有b锁,想获得a锁");
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"\t 成功获得a锁");
}
}
},"B").start();
}
}
如何排查死锁

用命令
- jps -l 查看当前进程运行状况
- jstack 进程编号 查看该进程信息


小总结
指针指向monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp,C++实现的
