可重入锁和不可重入锁概念和区别

2024-09-08 00:48
文章标签 概念 区别 不可 重入

本文主要是介绍可重入锁和不可重入锁概念和区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

可重入锁就是一个类的A、B两个方法,A、B都有获得统一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得该锁。

这种情景,可以是不同的线程分别调用这个两个方法。也可是同一个线程,A方法中调用B方法,这个线程调用A方法。

不可重入锁就是一个类的A、B两个方法,A、B都有获得统一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得不了该锁,必须等A方法释放掉这个锁。

https://blog.csdn.net/h2604396739/article/details/81255028

https://blog.csdn.net/rickiyeat/article/details/78314451

在java 中,synchronized和java.util.concurrent.locks.ReentrantLock是可重入锁。

当一个线程获得当前实例的锁lock,并且进入了方法A,该线程在方法A没有释放该锁的时候,是否可以再次进入使用该锁的方法B?

不可重入锁:在方法A释放锁之前,不可以再次进入方法B

可重入锁:在方法A释放该锁之前可以再次进入方法B

下面是其中一个例子

public class LockClient {

//    Lock lock = new Lock();

    ReinnerLock lock = new ReinnerLock();

    void read() throws InterruptedException{

        lock.lock();

        System.out.println("read");

        read1();

        lock.unLock();

    }

    

    void read1() throws InterruptedException{

        lock.lock();

        System.out.println("read1");

        lock.unLock();

    }

    public static void main(String[] args) {

        try {

//此时执行结果为 read和read1都能打印出来

//如果使用的锁是 Lock则报IllegalMonitorStateException

            new LockClient().read();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}

不可重入锁的实现:

public class Lock {

    

    private boolean isLocked = false;

    

    public void lock() throws InterruptedException{

        while(isLocked){//如果已经被获取锁,则当前线程只能等待

            wait();

        }

        isLocked = true;

    }

    

    public void unLock(){

        isLocked = false;

    }

}

可重入锁的实例:

public class ReinnerLock {

    

    private boolean isLocked = false;

    

    Thread thread = null;//当前的线程

    

    public void lock() throws InterruptedException{

        Thread currentThread = Thread.currentThread();

        while(isLocked && thread!=currentThread){

            wait();

        }

        isLocked = true;

        thread = currentThread;

    }

 

    public  void unLock(){

        isLocked = false;

    }

 

}
 

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁,下面是一个用synchronized实现的例子:

public class ReentrantTest implements Runnable {

    public synchronized void get() {
        System.out.println(Thread.currentThread().getName());
        set();
    }

    public synchronized void set() {
        System.out.println(Thread.currentThread().getName());
    }

    public void run() {
        get();
    }

    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        for(;;){
            new Thread(rt).start();
        }
    }
}

整个过程没有发生死锁的情况,截取一部分输出结果如下:

Thread-8492
Thread-8492
Thread-8494
Thread-8494
Thread-8495
Thread-8495
Thread-8493
Thread-8493

set()和get()同时输出了线程名称,表明即使递归使用synchronized也没有发生死锁,证明其是可重入的。

不可重入锁
不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。看到一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

    private AtomicReference<Thread> owner = new AtomicReference<Thread>();

    public void lock() {
        Thread current = Thread.currentThread();
        //这句是很经典的“自旋”语法,AtomicInteger中也有
        for (;;) {
            if (!owner.compareAndSet(null, current)) {
                return;
            }
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        owner.compareAndSet(current, null);
    }
}

代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。稍微改一下,把它变成一个可重入锁:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

    private AtomicReference<Thread> owner = new AtomicReference<Thread>();
    private int state = 0;

    public void lock() {
        Thread current = Thread.currentThread();
        if (current == owner.get()) {
            state++;
            return;
        }
        //这句是很经典的“自旋”式语法,AtomicInteger中也有
        for (;;) {
            if (!owner.compareAndSet(null, current)) {
                return;
            }
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        if (current == owner.get()) {
            if (state != 0) {
                state--;
            } else {
                owner.compareAndSet(current, null);
            }
        }
    }
}

在执行每次操作之前,判断当前锁持有者是否是当前对象,采用state计数,不用每次去释放锁。

ReentrantLock中可重入锁实现
这里看非公平锁的锁获取方法:

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //就是这里
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。
 

这篇关于可重入锁和不可重入锁概念和区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1146641

相关文章

GORM中Model和Table的区别及使用

《GORM中Model和Table的区别及使用》Model和Table是两种与数据库表交互的核心方法,但它们的用途和行为存在著差异,本文主要介绍了GORM中Model和Table的区别及使用,具有一... 目录1. Model 的作用与特点1.1 核心用途1.2 行为特点1.3 示例China编程代码2. Tab

Nginx指令add_header和proxy_set_header的区别及说明

《Nginx指令add_header和proxy_set_header的区别及说明》:本文主要介绍Nginx指令add_header和proxy_set_header的区别及说明,具有很好的参考价... 目录Nginx指令add_header和proxy_set_header区别如何理解反向代理?proxy

Java中&和&&以及|和||的区别、应用场景和代码示例

《Java中&和&&以及|和||的区别、应用场景和代码示例》:本文主要介绍Java中的逻辑运算符&、&&、|和||的区别,包括它们在布尔和整数类型上的应用,文中通过代码介绍的非常详细,需要的朋友可... 目录前言1. & 和 &&代码示例2. | 和 ||代码示例3. 为什么要使用 & 和 | 而不是总是使

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

Spring中@RestController和@Controller的使用及区别

《Spring中@RestController和@Controller的使用及区别》:本文主要介绍Spring中@RestController和@Controller的使用及区别,具有很好的参考价... 目录Spring中@RestController和@Controller使用及区别1. 基本定义2. 使

Qt 中 isHidden 和 isVisible 的区别与使用小结

《Qt中isHidden和isVisible的区别与使用小结》Qt中的isHidden()和isVisible()方法都用于查询组件显示或隐藏状态,然而,它们有很大的区别,了解它们对于正确操... 目录1. 基础概念2. 区别清见3. 实际案例4. 注意事项5. 总结1. 基础概念Qt 中的 isHidd

Spring、Spring Boot、Spring Cloud 的区别与联系分析

《Spring、SpringBoot、SpringCloud的区别与联系分析》Spring、SpringBoot和SpringCloud是Java开发中常用的框架,分别针对企业级应用开发、快速开... 目录1. Spring 框架2. Spring Boot3. Spring Cloud总结1. Sprin

Java中的runnable 和 callable 区别解析

《Java中的runnable和callable区别解析》Runnable接口用于定义不需要返回结果的任务,而Callable接口可以返回结果并抛出异常,通常与Future结合使用,Runnab... 目录1. Runnable接口1.1 Runnable的定义1.2 Runnable的特点1.3 使用Ru

Java中Runnable和Callable的区别和联系及使用场景

《Java中Runnable和Callable的区别和联系及使用场景》Java多线程有两个重要的接口,Runnable和Callable,分别提供一个run方法和call方法,二者是有较大差异的,本文... 目录一、Runnable使用场景二、Callable的使用场景三、关于Future和FutureTa

Java逻辑运算符之&&、|| 与&、 |的区别及应用

《Java逻辑运算符之&&、||与&、|的区别及应用》:本文主要介绍Java逻辑运算符之&&、||与&、|的区别及应用的相关资料,分别是&&、||与&、|,并探讨了它们在不同应用场景中... 目录前言一、基本概念与运算符介绍二、短路与与非短路与:&& 与 & 的区别1. &&:短路与(AND)2. &:非短