Lock锁和AQS

本文详细介绍了Java中的两种锁机制:synchronized关键字和Lock接口。synchronized通过ACC_SYNCHRONIZED标志和MONITORENTER/MONITOREXIT指令实现线程同步,而Lock接口的实现如ReentrantLock,依赖于AbstractQueuedSynchronizer(AQS)进行线程管理,AQS使用CAS操作和FIFO队列确保线程的公平或非公平访问。通过对锁的深入理解,开发者可以更好地选择适合的同步策略。

java 的锁分为两类:

  • 第一类是 synchronized 同步关键字,这个关键字属于隐式的锁,是 jvm 层面实现,使用的时候看不见;
  • 第二类是在 jdk5 后增加的 Lock 接口以及对应的各种实现类,这属于显式的锁,就是我们能在代码层面看到锁这个对象,而这些个对象的方法实现,大都是直接依赖 CPU 指令的,无关 jvm 的实现。

在这里插入图片描述

synchronized 原理

  • 修饰对象 对象锁
  • 修饰类 类锁
  • 修饰方法 实例方法 对象锁 静态方法 类锁
原理
修饰方法 - 依靠ACC_SYNCONSIZED标志

在执行一个方法之前,首先会识别ACC_SYNCONSIZED位,如果有,表明是一个同步方法,执行方法之前必须获取对象的Monitor,才会进去执行方法一当中内容,并且执行结束之前,其他线程无法获取当前Monitor,直到方法结束或者抛出异常。

public class Tues {
    public static int i ;
    public synchronized static void syncTask(){
        i++;
    }
}

例子

修饰代码快 - 依赖指令 MONITORENTER 和 MONITOREXIT 指令
public class Test {
    public static int i ;
    public  static void syncTask(){
        synchronized (Test.class){
            i++;
        }
    }
}

字节码: 注意其中的两个指令 MONITORENTER 和 MONITOREXIT 指令

  public static syncTask()V
    TRYCATCHBLOCK L0 L1 L2 null
    TRYCATCHBLOCK L2 L3 L2 null
   L4
    LINENUMBER 12 L4
    LDC Lcom/ll/payconter/intf/entity/Test;.class
    DUP
    ASTORE 0
    MONITORENTER
   L0
    LINENUMBER 13 L0
    GETSTATIC com/ll/payconter/intf/entity/Test.i : I
    ICONST_1
    IADD
    PUTSTATIC com/ll/payconter/intf/entity/Test.i : I
   L5
    LINENUMBER 14 L5
    ALOAD 0
    MONITOREXIT
   L1
    GOTO L6
   L2
   FRAME FULL [java/lang/Object] [java/lang/Throwable]
    ASTORE 1
    ALOAD 0
    MONITOREXIT
   L3
    ALOAD 1
    ATHROW
   L6
    LINENUMBER 15 L6
   FRAME CHOP 1
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 2
}

Lock锁 原理

Lock 完全用 Java 写成,在java这个层面是无关JVM实现的。虽然 Lock 缺少了 (通过 synchronized 块或者方法所提供的) 隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性。
锁的原理,主要是依靠聚合了一个同步器(AbstractQueuedSynchronizer 缩写为 AQS)的子类来完成线程访问控制的。
比如常见的ReentrantLock,其中内聚了Sync ,实现了AbstractQueuedSynchronizer进行线程的同步管理

 abstract static class Sync extends AbstractQueuedSynchronizer
AQS 怎么进行管理线程访问呢

AQS原理是:如果一个共享资源是处于空闲状态,就标记当前线程为访问线程,如果当前资源处于非空闲状态,就需要一个一个线程阻塞等待以及唤醒资源分配机制,AQS通过int类型变量表示状态,使用一个FIFO的队列进行线程管理,使用CAS进行状态改变。

什么事CAS

什么是 CAS? CAS 即比较并替换(Compare And Swap)。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

Lock锁就是需要AbstractQueuedSynchronizer子类进行实现

 /** Synchronizer providing all implementation mechanics */
    private final Sync sync;  手动设置公平锁或者非公平锁


/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();  默认是非公平锁
    }

ReentrantLock的三个实现
参考链接

Lock的原理AQS密切相关,许多Lock的实现基于AQSAQS即抽象队列同步器(AbstractQueuedSynchronizer),它为实现依赖于先进先出(FIFO)等待队列的阻塞相关同步器(信号量、事件等)提供了一个基础框架[^3][^4]。 Lock接口提供了比`synchronized`关键字更灵活的操作,而AQS为实现这些操作提供了基础。例如,`ReentrantLock`、`ReentrantReadWriteLock`等常见的Lock实现类,它们的内部都是基于AQS来实现的获取、释放以及线程的同步控制等功能[^4]。 以`ReentrantLock`为例,它的公平非公平实现都是通过继承AQS并重写相关方法来完成的。在获取时,会根据AQS的同步状态信息属性`state`来判断是否被占用,如果未被占用,则尝试通过CAS操作来修改`state`的值以获取;如果已被占用,则将当前线程放入AQS的等待队列中等待唤醒[^3][^4]。 ### 示例代码 以下是一个简单使用`ReentrantLock`的示例: ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(); public void doSomething() { lock.lock(); try { // 执行需要同步的代码 System.out.println("线程 " + Thread.currentThread().getName() + " 获得了"); } finally { lock.unlock(); System.out.println("线程 " + Thread.currentThread().getName() + " 释放了"); } } public static void main(String[] args) { LockExample example = new LockExample(); Thread thread1 = new Thread(example::doSomething); Thread thread2 = new Thread(example::doSomething); thread1.start(); thread2.start(); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值