JUC之可重入锁

可重入锁(又名递归锁)

可重入锁是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞

如果是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++实现的

相关推荐
棉花骑士16 小时前
【AI Agent】面向 Java 工程师的Claude Code Harness 学习指南
java·开发语言
爱敲代码的小鱼16 小时前
springboot(2)从基础到项目创建:
java·spring boot·spring
迈巴赫车主17 小时前
蓝桥杯19724食堂
java·数据结构·算法·职场和发展·蓝桥杯
i220818 Faiz Ul18 小时前
动漫商城|基于springboot + vue动漫商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·动漫商城系统
海兰18 小时前
【实战】MCP 服务在 Nacos 中注册状态分析与优化
android·java·github·银行系统·银行ai
Makoto_Kimur19 小时前
Java 打印模板大全
java·开发语言·排序算法
程序员榴莲19 小时前
Java(十)super关键字
java·开发语言
HAPPY酷19 小时前
Python高级架构师之路——从原理到实战
java·python·算法
Boop_wu19 小时前
[Java 算法 ] 链表
java·算法·链表
ybwycx19 小时前
SpringBoot下获取resources目录下文件的常用方法
java·spring boot·后端