可重入锁深入学习(有码)

举报
dragon-w 发表于 2024/07/12 09:06:36 2024/07/12
【摘要】 ​今天,梳理下java中的常用锁,但在搞清楚这些锁之前,先理解下 “临界区”。临界区在同步的程序设计中,临界区段活称为关键区块,指的是一个访问共享资源(例如:共享设备或是共享存储器)的程序片段,而这些共享资源又无法同时被多个线程访问的特性。---维基百科释义1:可重入互斥锁(维基百科的这个叫法更贴切),是互斥锁的一种,同一线程对其多次加锁不会产生死锁,可重入互斥锁也称为递归互斥锁或递归锁。 ...

今天,梳理下java中的常用锁,但在搞清楚这些锁之前,先理解下 “临界区”。

临界区

在同步的程序设计中,临界区段活称为关键区块,指的是一个访问共享资源(例如:共享设备或是共享存储器)的程序片段,而这些共享资源又无法同时被多个线程访问的特性。

---维基百科

释义1:可重入互斥锁(维基百科的这个叫法更贴切),是互斥锁的一种,同一线程对其多次加锁不会产生死锁,可重入互斥锁也称为递归互斥锁或递归锁。  ---维基百科

测试对象,等同于业务逻辑

package com.wangjianlong.algorithm.reentrantMutex;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ObjectA implements Runnable{

    public synchronized void func1(){
        log.info("start func1");
        func2();
        log.info("end func1");
    }


    public synchronized void func2(){
        log.info("start func2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("end func2");
    }

    @Override
    public void run() {
        func1();
        log.info("----------------------thread done--------------------------");
    }
}

执行测试

package com.wangjianlong.algorithm.reentrantMutex;

public class Test {

    public static void main(String[] args) {

        ObjectA aObject = new ObjectA();
        new Thread(aObject, "testThread1").start();
    }
}

执行结果:

14:22:24.715 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func1
14:22:24.718 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func2
14:22:29.725 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func2
14:22:29.725 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func1
14:22:29.725 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - ----------------------thread done--------------------------

Process finished with exit code 0

我们可以看到,ObjectA的func1和func2均被synchronized修饰,但在func1里调用func2,并不会产生死锁,func2正常执行。

释义2:可重入互斥锁,当且仅当尝试加锁的线程就是持有该锁的线程时,加锁操作就会成功。可重入互斥锁一般会记录被加锁的次数,只有执行相同次数的解锁操作才会真正解锁。

我们对调用测试进行调整,由刚才的一个线程执行,修改为4个线程并发执行

package com.wangjianlong.algorithm.reentrantMutex;

public class Test {

    public static void main(String[] args) {

        ObjectA aObject = new ObjectA();

        //同一个对象,启动四个线程执行
        new Thread(aObject, "testThread1").start();
        new Thread(aObject, "testThread2").start();
        new Thread(aObject, "testThread3").start();
        new Thread(aObject, "testThread4").start();
    }
}

看结果:

14:31:36.983 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func1
14:31:36.986 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func2
14:31:41.988 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func2
14:31:41.988 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func1
14:31:41.988 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - ----------------------thread done--------------------------
14:31:41.988 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func1
14:31:41.988 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func2
14:31:46.992 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func2
14:31:46.992 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func1
14:31:46.992 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - ----------------------thread done--------------------------
14:31:46.992 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func1
14:31:46.992 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func2
14:31:51.998 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func2
14:31:51.998 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func1
14:31:51.998 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - ----------------------thread done--------------------------
14:31:51.998 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func1
14:31:51.998 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - start func2
14:31:57.007 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func2
14:31:57.007 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - end func1
14:31:57.007 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectA - ----------------------thread done--------------------------

Process finished with exit code 0

分析:每个线程我都设置了名称,我们对照输出日志,可以观察出,每个线程内部synchronized 锁体现了出了可重入,但线程之间是互斥的,后面的线程必须等前面的线程执行完毕后才能获得ObjectA的执行权。


释义3:可重入互斥锁,是指在要给线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另外一个需要相同锁的方法,则该线程可以直接执行调用的方法(即可重入),而无需重新获得锁,而对于不同线程则相当于普通的互斥锁。java线程是基于“每线程(per-thread)”,而不是基于“每调用(per-invocation)”的,也就是说java为每个线程分配一个锁,而不是为每次调用分配一个锁

我们调整下代码,新建ObjectB,它和ObjectA的区别就是去掉了Runnable接口,而是使用单独的任务类Task1来调用,在Task1里我们进行的ObjectB的实例创建。

package com.wangjianlong.algorithm.reentrantMutex;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ObjectB{

    public synchronized void func1(){
        log.info("start func1");
        func2();
        log.info("end func1");
    }


    public synchronized void func2(){
        log.info("start func2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("end func2");
    }
}
package com.wangjianlong.algorithm.reentrantMutex;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Task1 implements Runnable{

    @Override
    public void run() {

        //类的实例化不等于创建线程
        ObjectB objectB =new ObjectB();
        objectB.func1();
        log.info("------------------------------执行线程done!------------------------------");
    }
}

执行结果:

14:43:02.460 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func1
14:43:02.463 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func2
14:43:02.460 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func1
14:43:02.460 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func1
14:43:02.460 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func1
14:43:02.464 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func2
14:43:02.464 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func2
14:43:02.464 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - start func2
14:43:07.480 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func2
14:43:07.480 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func1
14:43:07.480 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func2
14:43:07.480 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func2
14:43:07.480 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func1
14:43:07.480 [testThread1] INFO com.wangjianlong.algorithm.reentrantMutex.Task1 - ------------------------------执行线程done!------------------------------
14:43:07.480 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func1
14:43:07.480 [testThread4] INFO com.wangjianlong.algorithm.reentrantMutex.Task1 - ------------------------------执行线程done!------------------------------
14:43:07.480 [testThread3] INFO com.wangjianlong.algorithm.reentrantMutex.Task1 - ------------------------------执行线程done!------------------------------
14:43:07.480 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func2
14:43:07.480 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.ObjectB - end func1
14:43:07.480 [testThread2] INFO com.wangjianlong.algorithm.reentrantMutex.Task1 - ------------------------------执行线程done!------------------------------

Process finished with exit code 0

我们可以看到四个线程均立刻都得到了执行了,说明锁是与锁所在的类的实例相关的。

锁作为并发共享数据,保证一致性的工具。

java中的synchronized可重入锁,缺点是本线程有效,分布式就废了,如果用在数据库操作类应用存在缺陷。

操作DB的并发加锁应该考虑使用数据库乐观锁或者悲观锁;操纵内存的并发加锁应该考虑使用synchronized等Java锁,如果是分布式环境下,应考虑使用分布式锁。

----完-----

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。