• JUC并发编程系列详解篇十六(java中的其他锁)


    synchronized底层的锁

    无锁,偏向锁,轻量级锁,重量级锁,这四种锁是指锁的状态,专门针对synchronized的。偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。
    在这里插入图片描述
    更多请查看这篇文章:JUC并发编程系列详解篇十一(synchronized底层的锁)

    可重入锁 VS 非可重入锁

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

    Java 中 ReentrantLock 和synchronized 都是可重入锁,可重入锁的一个优点是在一定程度上可以避免死锁。

    我们先来看一段代码来说明一下 synchronized 的可重入性:

    public class Widget {
        public synchronized void doSomething() {
            System.out.println("方法1执行...");
            doOthers();
        }
    
        public synchronized void doOthers() {
            System.out.println("方法2执行...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上面的代码中,类中的两个方法都是被内置锁synchronized修饰的,doSomething()方法中调用doOthers()方法。因为内置锁是可重入的,所以同一个线程在调用doOthers()时可以直接获得当前对象的锁,进入doOthers()进行操作。

    如果是一个不可重入锁,那么当前线程在调用doOthers()之前需要将执行doSomething()时获取当前对象的锁释放掉,实际上该对象锁已被当前线程所持有,且无法释放。所以此时会出现死锁。

    而为什么可重入锁就可以在嵌套调用时可以自动获得锁呢?我们通过图示和源码来分别解析一下。

    还是打水的例子,有多个人在排队打水,此时管理员允许锁和同一个人的多个水桶绑定。这个人用多个水桶打水时,第一个水桶和锁绑定并打完水之后,第二个水桶也可以直接和锁绑定并开始打水,所有的水桶都打完水之后打水人才会将锁还给管理员。这个人的所有打水流程都能够成功执行,后续等待的人也能够打到水。这就是可重入锁。
    在这里插入图片描述
    但如果是非可重入锁的话,此时管理员只允许锁和同一个人的一个水桶绑定。第一个水桶和锁绑定打完水之后并不会释放锁,导致第二个水桶不能和锁绑定也无法打水。当前线程出现死锁,整个等待队列中的所有线程都无法被唤醒。
    在这里插入图片描述
    首先ReentrantLock和NonReentrantLock都继承父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。

    当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。

    释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。

    在这里插入图片描述

    独享锁(排他锁)VS 共享锁

    独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。

    共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

    下图为ReentrantReadWriteLock的部分源码:
    在这里插入图片描述
    我们看到ReentrantReadWriteLock有两把锁:ReadLock和WriteLock,由词知意,一个读锁一个写锁,合称“读写锁”。再进一步观察可以发现ReadLock和WriteLock是靠内部类Sync实现的锁。Sync是AQS的一个子类,这种结构在CountDownLatch、ReentrantLock、Semaphore里面也都存在。

    在ReentrantReadWriteLock里面,读锁和写锁的锁主体都是Sync,但读锁和写锁的加锁方式不一样。读锁是共享锁,写锁是独享锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriteLock的并发性相比一般的互斥锁有了很大提升。

    文章来源

    • Java全栈知识体系中的java并发-java中所有的锁
    • 微信公众号 - java建设者的文章《看完你就应该能明白的悲观锁和乐观锁》
    • 微信公众号 - 石杉的架构笔记的文章《不懂什么是 Java 中的锁?看看这篇你就明白了!》

    基于以上三位的文章内容,个人进行归纳总结,仅做日常学习分享,不做其他用途。

  • 相关阅读:
    【Linux 线程介绍】
    JavaScript小技能:变量
    光流法( Optical Flow Method)
    一个策略模式Demo
    《React vs. Vue vs. Angular:2023年的全面比较》
    C++模拟揭秘刘谦魔术,领略数学的魅力
    EV代码签名申请步骤
    springMVC第一天
    天天提交代码,git commit 提交时能规范一下吗?
    微服务实战微服务网关Zuul入门与实战
  • 原文地址:https://blog.csdn.net/m0_46198325/article/details/126811963