读书笔记:《JAVA并发编程的艺术》第5章 Java中的锁

本文详细解析了Java并发包中的Lock接口,包括其与synchronized关键字的区别,以及如何通过AbstractQueuedSynchronizer (AQS) 实现自定义同步组件。探讨了ReentrantLock的实现原理,包括公平与非公平锁的选择,以及独占式与共享式获取同步状态的方法。

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


Lock接口的实现基本都是通过聚合了一个同步器的子类来完成线程访问控制的。

5.1 Lock接口

在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5之后,并发包中新增了Lock接口用来实现锁功能,提供了显示地锁获取和锁释放。

//java.util.concurrent.locks包
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
public interface Lock {
	void lock();
	void lockInterruptibly() throws InterruptedException;
	boolean tryLock();//尝试非阻塞地获取锁,如果能够获取则立刻返回true,否则返回false
	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	void unlock();
	Condition newCondition();//获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁
}

Lock接口提供了synchronized不具备的特性:尝试非阻塞地获取锁、能被中断地获取锁、超时获取锁。
ReentrantLock包含了内部类Sync
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}

5.2队列同步器

队列同步器AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架,使用private volatile int state来表示同步状态,通过内置的FIFO队列来完成线程排队工作。队列同步器的主要使用方式是继承 (其本身继承了AbstractOwnableSynchronizer),子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态做更改,这时需要使用同步器提供的三个方法(getState()、setState(int newState)和compareAndSetState(int expect, int update))。子类推荐被定义为自定义同步组件的静态内部类,如:
abstract static class Sync extends AbstractQueuedSynchronizer {}
同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。

Node结点

Node结点用来保存获取同步状态失败的线程引用、等待状态以及前驱结点和后继结点。

static final class Node {
	volatile int waitStatus;
	volatile Node prev;
	volatile Node next;
	volatile Thread thread;
	Node nextWaiter;
	...
}

队列同步器的实现

同步器拥有首结点和尾结点,没有成功获取同步状态的线程将会成为结点加入该队列的尾部。

    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;

同步器提供了一个基于CAS的设置尾结点的方法:compareAndSetTail(Node expect,Node update)

private final boolean compareAndSetTail(Node expect, Node update) {
	return unsafe.compareAndSwapObject(this, tailOffset, expect, update);//本地方法,底层实现
    }

独占式同步状态获取与释放

public final void acquire(long arg) {
	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

该方法会调用tryAcquire方法,如果调用失败,则构造同步结点并通过addWaiter方法将该结点加入到同步队列的尾部,最后调用acquireQueued方法,使得该结点以“死循环”的方式获取同步状态。

final boolean acquireQueued(final Node node, long arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())  //如果前驱结点中断或取消,则跳过
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
自旋过程

结点进入同步队列之后,就进入了一个自旋的过程,每个结点都在自省地观察,当条件满足获取到了同步状态,就退出。
即:停止自旋的条件是前驱结点为头结点且成功获取了同步状态。

释放同步状态
public final boolean release(long arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}

该方法执行会唤醒头结点的后继结点线程,unparkSuccessor(Node node)方法使用LockSupport来唤醒处于等待状态的线程。

共享式同步状态获取与释放

读操作可以是共享式访问。通过acquireShared获取,通过releaseShared释放。

独占式超时获取同步状态

private boolean doAcquireNanos(long arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

5.3重入锁

重入锁ReentrantLock是支持重进入的锁,除此之外还支持获取锁时的公平和非公平性选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值