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

相关推荐
A懿轩A1 小时前
【Java 基础编程】Java 正则表达式实战:Pattern/Matcher、元字符与常用正则,验证与提取必备
java·开发语言·正则表达式
予枫的编程笔记2 小时前
【Kafka进阶篇】Spring Boot Kafka客户端踩坑记:自定义序列化器+ContainerFactory调优指南
java·spring boot·kafka·java21·并发消费·kafka客户端·自定义序列化器
黎雁·泠崖2 小时前
Java 集合入门:Collection & List 接口超详细讲解
java·list
Cxiaomu2 小时前
React Native项目(Android )集成虹软 ArcFace(人脸识别增值版 5.0 Java)
android·java·react native
柒.梧.2 小时前
Java代理模式精讲:静态代理+JDK动态代理
java·开发语言·代理模式
悠闲蜗牛�2 小时前
Go语言高并发编程深度实战:从原理到性能优化的完整指南
java·运维·数据库
苡~2 小时前
【openclaw+claude系列02】全景拆解——手机、电脑、AI 三者如何协同工作
java·人工智能·python·智能手机·电脑·ai编程
LSL666_2 小时前
5 Redis通用命令
java·开发语言·redis·命令
rannn_1112 小时前
【Redis|基础篇】初识、Redis的安装与启动、Redis命令、Java客户端
java·redis·后端·缓存·nosql