Java中的锁(深入理解AQS)

一、队列同步器 AbstractQueuedSynchronizer

队列同步器 AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO 队列来完成资源获取线程的排队工作。

1. Lock接口

Java SE 5 之后,并发包中新增了 Lock 接口(以及相关实现类),用来实现锁功能,它提供了与 synchronized 关键字类似的同步功能,只是在使用时需要显式地获取锁和释放锁。虽然它缺少了隐式获取释放锁的便捷性,但是却拥有了锁获取与同步的可操作性可中断的获取锁以及超时获取锁等多种synchronized 关键字不具备的功能

Lock 接口提供的synchronized 关键字不具备的主要特性

  • 尝试非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
  • 能被中断的获取锁:获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会抛出,同时锁会被释放
  • 超时获取锁:在指定截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回

2. AQS的使用

AQS的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法,这样就大大降低了实现一个可靠自定义同步组件的门槛

package lockTest;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Description: AQS
 * @Author: Aiguodala
 * @CreateDate: 2021/4/19 10:51
 */

public class AQSTest {

    private static final MyLock LOCK = new MyLock();
    public static void main(String[] args) {
        new Thread(() -> {
            LOCK.lock();
            try {
                System.out.println("第一个线程获取锁");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("第一个线程释放锁");
                LOCK.unlock();
            }
        }).start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            LOCK.lock();
            try {
                System.out.println("第二个线程获取锁");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("第二个线程释放锁");
                LOCK.unlock();
            }
        }).start();




    }
}
class MyLock implements Lock {

    /**
     * 静态内部类,自定义同步器
     */
    private static class Sync extends AbstractQueuedSynchronizer {

        /**
         * 是否处于占用状态
         * @return
         */
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        /**
         * 当状态为 0 的时候获取锁
         * @param arg
         * @return
         */
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0,1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /**
         * 释放锁,将状态置为 0
         * @param arg
         * @return
         */
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            // setState 放在最后是因为 state 变量是volatile 修饰的,加入写屏障可以保证可见性
            setState(0);
            return true;
        }

        /**
         * 每个Condition 都包含了一个Condition队列
         * @return
         */
        Condition newCondition () {
            return new ConditionObject();
        }
    }

    /*********** 以下方法只需要将操作代理到 Sync 对象上 ***********/

    /**
     * 创建锁对象
     */
    private final Sync sync = new Sync();

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }

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

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

3. AQS 实现原理

3.1 同步队列

同步器依赖内部的同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态的等信息构造成一个节点( Node )并将其加入到同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

Node 主要字段

        /**
         * 等待状态
         */
        volatile int waitStatus;

        /**
         * 前驱节点,当节点被加入到等待队列时被添加
         */
        volatile Node prev;

        /**
         * 后继节点
         */
        volatile Node next;

        /**
         * 获取同步状态的线程
         */
        volatile Thread thread;

        /**
         * 等待队列的后继节点,如果当前节点是共享的,那么这个字段将是一个SHARED常量,
         * 也就是说节点类型(独占或者共享)和等待队列中的后继节点共用一个字段
         */
        Node nextWaiter;
  • waitStatus 的状态有:
        /**
         * 由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待
         * 节点变成该状态将不会变化
         */
        static final int CANCELLED =  1;
        
        /**
         * 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消
         * 将会通知后继节点,使得后继节点得以运行
         */
        static final int SIGNAL    = -1;
        
        /**
         * 节点在等待队列中,节点线程等待在Condition上
         * 当其他线程对Condition调用了signal() 方法后,该节点会从等待队列转移到同步队列中
         */
        static final int CONDITION = -2;
        
        /**
         * 表示下一次共享式同步状态会被无条件传播下去
         */
        static final int PROPAGATE = -3;

队列结构

在这里插入图片描述

  • 同步器包含两个节点类型的引用,一个指向头结点,一个指向尾结点
  • 一个线程成功获取锁后,其他线程无法获得锁而进入同步队列,这个操作必须保证线程安全,利用CAS加入尾结点
  • 首节点完成任务后释放锁并且唤醒后继节点,因为只有一个线程能够获得锁,所以不需要使用CAS

3.2 独占式同步状态获取和释放

① acquire( int arg)

获取同步状态,该方法对中断不敏感,也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移除

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • 首先调用自己重写的tryAcquire(arg) 方法,保证线程安全的获取状态并且修改为同步状态
  • 如果失败就构造节点(独占式Node.EXCLUSIVE) 通过addWaiter 加入到队列尾部
  • 最后调用acquireQueued () 方法使得节点以死循环方式获取同步住哪个台,获取不到则阻塞节点保存的线程。

addWaiter (Node node)

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 备份tail节点引用
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            // 利用CAS 设置尾结点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 不成功进入enq
        enq(node);
        return node;
    }

enq(node)

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                // 只有通过CAS成功添加节点才会退出
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 死循环,不断自旋,获取同步状态才可以退出
            for (;;) {
            	// 获取node 的前驱节点
                final Node p = node.predecessor();
                // 只有前驱节点是头结点,才能尝试获取同步状态
                if (p == head && tryAcquire(arg)) {
                	// 将当前节点设置为头结点
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // shouldParkAfterFailedAcquire 将前驱节点状态置为 -1 用于标记谁来唤醒该被阻塞地节点
                // 如果 shouldParkAfterFailedAcquire 返回true 则进入parkAndCheckInterrupt 节点被阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

② release(int arg)

当前线程获取同步状态并执行相应逻辑之后,需要释放同步状态,使得后续节点能够继续获取同步状态。

    public final boolean release(int arg) {
    	// 调用自己重写的tryRelease(arg) 方法
        if (tryRelease(arg)) {
            Node h = head;
            // 唤醒处于等待的线程
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

3.3 共享式同步状态获取和释放

共享式获取与独占式获取最重要的区别在于同一时刻是否能有多个线程同时获取到同步状态。
例如:读文件是共享式,而写文件是独占式

① acquireShared(int arg)

    public final void acquireShared(int arg) {
    	// 调用自定义的tryAcquireShared(arg) 方法,返回值大于0 则获取同步状态
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }


    private void doAcquireShared(int arg) {
    	// 构造SHARED 共享式节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 自旋
            for (;;) {
            	// 获得前驱节点
                final Node p = node.predecessor();
                // 如果前驱节点是头结点,则尝试获取同步状态
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

② acquireShared(int arg)

    public final boolean releaseShared(int arg) {
    	// 调用自己重写的tryReleaseShared(arg) 方法尝试释放同步状态
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                	// 必须确保同步状态的线程安全释放,因为释放同步操作的线程
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

3.4 独占式超时获取同步状态

通过调用同步器的doAcquireNanos(int arg, long nanosTimeout) 方法,可以超时获取同步状态,提供了synchronized所不具备的特性

实现逻辑和独占式获取同步状态大体相同,只是在获取失败时会判断是否超时

    private boolean doAcquireNanos(int 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);
        }
    }
已标记关键词 清除标记
相关推荐
<p> <strong><span style="font-size:16px;color:#003399;">会用Python分析金融数据 or 金融行业会用Python</span></strong> </p> <p> <strong><span style="font-size:16px;color:#003399;">职场竞争力更高</span></strong> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdnimg.cn/202012231042221925.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <strong><span style="font-size:16px;color:#003399;">Python金融数据分析入门到实战</span></strong> </p> <p> <strong><span style="font-size:16px;color:#003399;">Get√金融行业数据分析必备技能</span></strong> </p> <p> <img src="https://img-bss.csdnimg.cn/202012231042438069.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <strong><span style="font-size:16px;color:#003399;">以股票量化交易为应用场景</span></strong> </p> <p> <strong><span style="font-size:16px;color:#003399;">完成技术指标实现全过程</span></strong> </p> <p> <br /> </p> <p> <span style="font-size:14px;">课程选取股票量化交易为应用场景,由股票数据获取、技术指标实现,逐步进阶到策略设计</span><span style="font-size:14px;">和回测,由浅入深、由技术到思维地为同学们讲解Python金融数据分析在股票量化交易应用</span><span style="font-size:14px;">。</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <img src="https://img-bss.csdnimg.cn/202012231043183686.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <strong><span style="font-size:16px;color:#003399;">以Python为编程语言</span></strong> </p> <p> <strong><span style="font-size:16px;color:#003399;">解3大主流数据分析工具</span></strong> </p> <p> <br /> </p> <p> <span style="font-size:14px;">Python做金融具有先天优势,课程提取了Python数据分析工具NumPy、Pandas及可视化工具</span><span style="font-size:14px;">Matplotlib关键点详细讲解,帮助同学掌握数据分析关键技能。</span> </p> <p> <img src="https://img-bss.csdnimg.cn/202012231043472858.png" alt="" /> </p> <p> <strong><span style="font-size:16px;color:#003399;"><br /> </span></strong> </p> <p> <strong><span style="font-size:16px;color:#003399;">2大购课福利</span></strong> </p> <p> <strong><span style="font-size:16px;color:#003399;"><br /> </span></strong> </p> <p> <img src="https://img-bss.csdnimg.cn/202012300628195864.png" alt="" /> </p>
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页