java并发包中的AbstractQueuedSychronizer

本文详细介绍了Java并发包中的AbstractQueuedSychronizer(AQS)及其在ReentrantLock、CountDownLatch等同步组件中的应用。AQS通过volatile state变量管理和维护线程的同步,支持独占和共享两种资源获取方式。同时,文章讲解了自定义同步器的实现,包括尝试获取和释放资源的方法。此外,还讨论了锁的公平性和非公平性,以及死锁、活锁的概念。最后,对比了悲观锁和乐观锁,并概述了各种锁的类型,如可重入锁、读写锁等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java并发包中的AbstractQueuedSychronizer

简介

AQS: AbstractQueuedSychronizer(抽象的队列同步器)是java的J.U.C包中Lock、Semaphore、ReentrantLock等这些锁都是基于AQS框架实现的,

核心

AbstractQueuedSychronizer(抽象的队列同步器)
核心: volatile int state (资源)
FIFO(进程等待队列)

对资源(state)有两种共享方式:
exclusive独占锁: ReentrantLock
share共享锁: CountDownLatch

自定义同步器

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取和释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队、唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

isHeldExclusively(): 该线程是否正在独占资源

tryAcquire(int): 独占方式。尝试获取资源,成功返回true,失败返回false

tryRelease(int):独占方式,释放资源,成功返回true,失败返回false

tryAcquireShared(int):共享方式,尝试获取资源,负数表示失败,0表示成功,但是没有剩余资源,正数表示成功,且有剩余资源

tryRelaseShared(int):共享方式,尝试释放资源,如果释放后允许唤醒后续等待节点返回true,否则返回false

ReentrantLock

 ReentrantLock reentrantLock = new ReentrantLock();
 // 加锁
 reentrantLock.lock();
 // 释放锁
 reentrantLock.unlock();

加锁

lock & unlock
public void lock() {
    sync.lock();
}

final void lock() {
    acquire(1);
}

public final void acquire(int arg) {
    // tryAcquire获取到锁 直接返回
    // tryAcquire没有获取到锁,acquireQueued当前线程加入等待队列
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire

fairSync 公平锁

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 当前没有线程获取到锁
        // CAS compare and set 
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //设置当前线程获取到锁 
            setExclusiveOwnerThread(current);
                return true;
        }
    }
    // 可重入锁,当前线程再次获取到锁
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
        //设置 state值,为当前重入次数 
        setState(nextc);
        return true;
    }
    //无法获取到锁
    return false;
}

acquireQueued
final boolean acquireQueued(final Node node, int arg) {
    // 标记是否获取到锁资源
    boolean failed = true;
    try {
        // 标记等待过程中是否被中断过
        boolean interrupted = false; 
        // 自旋锁
        for (;;) {
            // 当前尝试获取锁的节点的前一个节点
            final Node p = node.predecessor();
            //如果当前进程是队列中的老二,那么便可尝试获取锁(等待第一个线程释放锁就可以获取到锁了)
            if (p == head && tryAcquire(arg)) {
                // 可以获取到锁,将head指向该节点
                setHead(node);
                p.next = null; // help GC
                // 表示成功获取到锁了
                failed = false;
                // 返回等待过程中是否被中断过
                return interrupted;
            }
            // 是否可以挂起,挂起后等待唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 有被中断过
                interrupted = true;
            }
    } finally {
        //自旋锁没有获取到锁,可能是超时或中断,那么可以取消节点在队列中的等待
        if (failed)
            cancelAcquire(node);
        }
}

isHeldExclusively
//该线程是否正在独占资源
protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}

class AbstractOwnableSynchronizer {
    private transient Thread exclusiveOwnerThread;

    //获取当前获取到锁的线程
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

释放锁

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        // 表示锁已经完全释放
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 当前线程是不是获取到锁的进程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // state等于0表示占领锁的线程已经全部(重入锁)被释放了
    if (c == 0) {
        free = true;
        // exclusiveOwnerThread 设置为Null
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

synchornized

synchornized关键字是同步锁,可以加在对象、类、方法、代码块,静态方法;synchornized是可重入的锁。

对象锁: 对象、方法、代码块
类锁:类、静态方法,

public Test {
    public void demo() {
        // 同一个jvm只有一个线程某刻可以获取到锁
        synchornized(Test.class) {
            // do something
        }
    }
}
静态方法
public Test {
    // 同一个jvm只有一个线程某刻可以获取到锁
    public  static  synchornized void demo() {
        //do something
    }
}
对象
public Test {
    public void demo() {
        // 同一个Test实例只有一个线程某刻可以获取到锁
        synchornized(this) {
            // do something
        }
    }
}

public void demo1)_ {
    Test test1 = new Test()
    Test test2 = new Test()
    // test1与test2可以同时获得锁,因为他们获得是两把锁
    test1.demo();
    test2.demo()    
}

方法
public Test {
    // 同一个Test实例只有一个线程某刻可以获取到锁
    public  synchornized void demo() {
        //do something
    }
}
代码块
public Test {
    // 同一个Test实例只有一个线程某刻可以获取到锁
    public void demo(Test test) {
        synchornized(test) {
            //do something
        }
    }
}
原理

当代码块使用synchornized编译后会在代码中加入monitor监视器。

编译后的代码

monitorenter
// 代码块编译后的指令
monitorexit

当方法声明使用synchronized时,方法编译后会设置ACC_SYNCHRONIZED来标识
当方法调用时,调用指针将会检查方法的ACC_SYNCHRONIZED标识是否被设置了,如
果被设置了,执行线程将先持有monitor,然后在执行方法,最后方法完成时释放monitor。

synchronized是可重入的锁,因此当持有的锁的线程再一次获取到锁,monitor的计数器仍然会加1。
每一次释放monitor计数器会减一。

这里看起synchronized重入的实现和AQS的state思想是一致的。

死锁

两辆车在桥的两端,如果一方不让出路来,那么两辆车都过不了。这就是死锁。

 生动的一个解释:
    消费者:先给商品再付款
    商家: 先付款再给商品
    双方都不愿意让步,就会产生死锁。

活锁

两辆车在桥的两端,两方都在给对方让路,那么两辆车都过不去,这就是活锁

公平与非公平锁

公平锁

多个线程按照申请资源的顺序来获取锁,先到先得

// 公平锁
ReentrantLock lock = new ReentrantLock(true);
非公平锁

多个线程争抢资源,谁先抢到谁获得资源,非公平锁并发度高。
synchronized和ReentrantLock是非公平锁

// 非公平锁
ReentrantLock lock = new ReentrantLock();

重入与不可重入

可重入锁

同一个线程可以多次获取同一把锁。

// code show 
public void demo() {
    for (int i = 0; i < 10; i++) {
        synchronized(this) {
            // do something
        } 
    }

}

不可重入锁

同一个线程如果要再次获得锁,必须等待之前获取到的锁释放锁。不可重入锁很容易引起死锁。(如上可重入锁的代码如果加锁代码块使用不可重入锁,就是引起死锁)。

自旋锁

一个线程尝试获取锁,获取不到,不会立即阻塞,而是采用循环的方式尝试去获取,acquireQueued方法中的for (;;) 就是在自旋。
自旋锁可以减少线程切换的上下文开销,但是如果自旋时间过长会非常耗费CPU的性能。

乐观与悲观锁

悲观锁

在操作资源前先加锁,例如数据库中的行锁,一个线程读取到准备操作资源,其他线程就只能阻塞等待了。

悲观锁认为别人在操作资源的时候会对资源进行修改,所以在它持有资源的时候,会阻塞其他线程。

乐观锁

乐观锁通过版本号来实现对资源的修改,当版本号不对则重新读取再次修改。不会阻塞其他线程。
如线程A,B同时读取到资源id = 2, data = 1, 他们都做加一操作。
则:
A: update set data = 2, version = version + 1 where id = 2 and version = 1 成功
B: update set data = 2, version = version + 1 where id = 2 and version = 1 失败

CAS: compare and set 也是乐观锁, 因为CAS没有版本号所以容易发生ABA问题。

共享锁与排他锁

共享锁

多个线程可以获取到同一把锁,同一个把锁会被多个线程获取。也就是读锁
如CountDownLatch

排他琐

一把锁只能被一个线程获取。某一个时刻只有一个线程可以获取到锁。也称独占锁。

J.U.C提供的一些锁

CyclicBarrier 栅栏
CountDownLatch 闭锁
Semaphore 信号量
ReentrantLock: 可重入锁
ReentrantReadWriteLock : 读写锁

参考

从ReentrantLock的实现看AQS的原理及应用

Java并发之AQS详解

Java常见锁类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值