并发编程(4)——AbstractQueuedSynchronizer
AQS
内部类Node
等待队列是CLH有锁队列的变体。
waitStatus的几种状态:
  static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;
以下面的测试程序为例,简单介绍一下同步队列的变化:
	@Test
    public void test() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ReentrantLock lock = new ReentrantLock();
        try {
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        lock.lock();
                    }
                }, "线程 " + i
                ).start();
            }
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
//            lock.unlock();
        }
我们发现,ReentrantLock的lock方法如下:
		final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
由于是独占的获取,因此只有一个线程会通过CAS成功获取state,因此其它四个线程都会进入acquire(1)方法。acquire(int arg)是AQS的模板方法,方法内容如下:
	public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
以非公平锁为例,tryAcquire实际调用nonfairTryAcquire.该方法可以看出,首先还是通过CAS来获取state,如果是owner是之前的那个线程的话,允许重入,acquire加acquires。
		final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
继续回到刚才的acquire方法,会发现tryAcquire方法返回false,调用addWaiter方法:
	private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
假设最开始是线程0获取了state,后面来的依次是线程1、线程2、线程3、线程4.
线程1进入addWaiter方法,tail为空,进入enq方法,这里会初始化AQS中的head和tail,例子里的话head是一个new Node对象,tail的Node对象是new Node(“线程1”, mode)对象。
	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;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
继续,执行完addWaiter方法之后会进入acquireQueued方法:
	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// 找到node的前辈节点
                final Node p = node.predecessor();
                // 如果线程0不释放,则该不会进入
                // 如果线程0释放state,并且p是head,也就是同步队列中的第一个任务,这个时候获取state成功,将node设置为AQS的head,返回false,结束acquire方法。
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 第一个判断,判断node的前辈节点是否为-1或者大于0,否则设置状态为-1,再下一次循环时,返回true进入第二个判断
                // 第二个判断,将node对应的线程park,即设置为wait状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
其余的线程2/3/4依次在同步队列上,类似于:
+-----------+                    +-----------+               + -----------+          + -----------+     +-----------+
| head    |                  |    线程1 |               |    线程2  |         |     线程3 |     |    线程4|
+-----------+                    +-----------+               + -----------+          + -----------+     +-----------+
以下面测试程序为例,再看unlock方法(顺便提一下,idea调试多线程需要将断点处的all改为thread, 程序中的countdownlatch是为了不让test线程结束,导致无法调试)调试时看到一个线程进入release方法,其余四个线程处于wait状态,说明程序正确了。
	@Test
    public void test() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ReentrantLock lock = new ReentrantLock();
        try {
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        lock.lock();
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if ( lock.isHeldByCurrentThread()) {
                            lock.unlock();
                        }
                    }
                }, "线程 " + i
                ).start();
            }
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
        }
    }
讲完了lock()方法,再看unlock()方法,调用release(int arg)方法
	public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
tryRelease(arg)不再赘述,不过是释放获得的许可,将state设置为0(一般情况下,有些是重入,需要多调用几次unlock才行),置空独占线程。
进入if内部,调用unparkSuccessor方法
	private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
正常情况下,唤醒同步队列中的第一个任务线程
acquireShared
上面讲的是独占获取,接下来看一下共享获取
这里以ReentrantReadWriteLock为例
简单介绍一下内部类,包含一个同步器Sync,以及公平及非公平类FairSync与NonfairSync,ReadLock和WriteLock
因为读锁非独占,因此lock方法对应的是sync.tryAcquireShared(1),写锁则相反。
其他
AQS使用了模板方法设计模式。
并发编程(4)——AbstractQueuedSynchronizer的更多相关文章
- 并发编程 20—— AbstractQueuedSynchronizer 深入分析
		Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ... 
- Java并发编程系列-AbstractQueuedSynchronizer
		原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/10566625.html 一.概述 AbstractQueuedSynchronizer简 ... 
- Java并发编程(2) AbstractQueuedSynchronizer的设计与实现
		一 前言 上一篇分析AQS的内部结构,其中有介绍AQS是什么,以及它的内部结构的组成,那么今天就来分析下前面说的内部结构在AQS中的具体作用(主要在具体实现中体现). 二 AQS的接口和简单示例 上篇 ... 
- Java并发编程(2) AbstractQueuedSynchronizer的内部结构
		一 前言 虽然已经有很多前辈已经分析过AbstractQueuedSynchronizer(简称AQS,也叫队列同步器)类,但是感觉那些点始终是别人的,看一遍甚至几遍终不会印象深刻.所以还是记录下来印 ... 
- 并发编程 01—— ThreadLocal
		Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ... 
- 并发编程 02—— ConcurrentHashMap
		Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ... 
- 并发编程 04——闭锁CountDownLatch 与 栅栏CyclicBarrier
		Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ... 
- 并发编程 05—— Callable和Future
		Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ... 
- 并发编程 06—— CompletionService :Executor 和 BlockingQueue
		Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ... 
- 并发编程 10—— 任务取消 之 关闭 ExecutorService
		Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ... 
随机推荐
- Linux权限_用户_和用户组
			Linux中用户UID就判断操作系统中用户的身份. Centos7.x: 0:超级管理员 1-999:系统用户(包含Linux中自带服务) 1000以上 普通用户 Centos6.x : Root ... 
- 字符串匹配问题(暴力,kmp)
			对于字符串的匹配问题,现在自己能够掌握的就只有两种方法, 第一种就是我们常用的暴力匹配法,那什么是暴力匹配法呢? 假设我们现在有一个文本串和一个模式串,我们现在要找出模式串在文本串的哪个位置. 文本串 ... 
- 在FPS游戏中,玩家对音画同步感知的量化与评估
			前言 在游戏测试中,音画同步测试是个难点(所谓游戏音画同步:游戏中,音效与画面的同步程度),现在一般采用人工主观判断的方式测试,但这会带来2个问题: 无法准确量化,针对同一场景的多次测试结果可能会相反 ... 
- @ApiImplicitParam注解
			@Api:用在请求的类上,表示对类的说明 tags="说明该类的作用,可以在UI界面上看到的注解" value="该参数没什么意义,在UI界面上也看到,所以不需要配置&q ... 
- 上传文件不落地转Base64字符串
			1. 问题描述 因需调用第三方公司的图像识别接口,入参是:证件类型.图像类型.图片base64字符串,采用http+json格式调用. 本来采用的方式是:前端对图片做base64处理,后端组装下直接调 ... 
- MyBatis:choose标签的用法
			<!-- 4.2 choose用法 需求: 在已有的sys_user表中,除了主键id外,我们认为user_name也是唯一的, 所有的用户名都不可以重复.现在进行如下查询:当参数id有值的时候 ... 
- CF1194D 1-2-K Game (博弈论)
			CF1194D 1-2-K Game 一道简单的博弈论题 首先让我们考虑没有k的情况: 1. (n mod 3 =0) 因为n可以被分解成若干个3相加 而每个3可以被分解为1+2或2+1 所以无论A出 ... 
- cozmo 入坑日记及开发环境搭建
			前几日,朋友在群里发了一个机器人的小视频,视频里机器人可以对话,可以推箱子,开心以后会哈哈大笑,非常有趣. 详细了解里一下,这是个叫 cozmo 的智能机器人,可以配合 SDK 用 python 编程 ... 
- Contos7 常用命令
			```pythoncentos常用命令: 查看所有运行的单元:systemctl list-units 查看所有单元:systemctl list-units --all 查看所有启动的服务:syst ... 
- 洛谷 P1970 花匠
			题目描述 花匠栋栋种了一排花,每株花都有自己的高度.花儿越长越大,也越来越挤.栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较别致. 具体而 ... 
