AQS

AQS(AbstractQueuedSynchronizer)是 java.util.concurrent的基础。J.U.C中宣传的封装良好的同步工具类SemaphoreCountDownLatchReentrantLockReentrantReadWriteLockFutureTask等虽然各自都有不同特征,但是简单看一下源码,每个类内部都包含一个如下的内部类定义:

abstract static class Sync extends AbstractQueuedSynchronizer

同时每个类内部都包含有这样一个属性,连属性名都一样!注释已经暗示了,该类的同步机制正是通过这个AQS的子类来完成的。不得不感叹:“每个强大的同步工具类,内心都有一把同样的锁!

/** All mechanics via AbstractQueuedSynchronizer subclass */

private final Sync sync;

几种同步类提供的功能其实都是委托sync来完成。有些是部分功能,有些则是全部功能,ReentrantLock的Sync属性就重写AQS部分方法和添加了一些方法。

AQS类中三个重要的属性:

private transient volatile AbstractQueuedSynchronizer.Node head;
private transient volatile AbstractQueuedSynchronizer.Node tail;
private volatile int state;

Node类型的head和tail是一个FIFO的wait queue;一个int类型的状态位state。到这里也能猜到AQS对外呈现(或者说声明)的主要行为就是由一个状态位一个有序队列来配合完成(不同的锁state和有序队列含义不一样,比如在ReentrantLock中,state表示该锁被重入的次数,有序队列保存的等待中的线程的顺序)。 最简单的读一下主要的四个方法:

//获取排他锁
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //释放排他锁
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } //获取共享锁
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } //释放共享锁
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }

ReentrantLock

ReentrantLock对外的主要方法是lock(),tryLock()和unlock()方法,当然还有其他变种的lockInterruptibly()、tryLock(long timeout, TimeUnit unit)等。

(重点)注意:在ReentrantLock中,这些所有的lock操作都调用的AQS, AQS底层调用是sun.misc.Unsafe对象(有大量的native方法和非native方法)进行同步操作,如unsafe.compareAndSetState

lock()方法的功能是获取锁。如果没有线程使用则立即返回,并设置state为;如果当前线程已经占有锁,则state加1;如果其他线程占有锁,则当前线程不可用,等待。

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

tryLock()的功能是:如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,立即返回值 false。

public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}

unlock()的功能是尝试释放锁,如果当前线程占有锁则count减一,如果count为0则释放锁。若占有线程不是当前线程,则抛异常。

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

可以看到也是借助Sync来完成,我们下面详细看下Sync是如何实现这些”规定”的需求的。ReentrantLock的构造函数告诉我们,其支持公平和非公平两种锁机制。

public ReentrantLock(boolean fair) {
sync = (fair)? new FairSync() : new NonfairSync();
}

在该类中对应定了两种FairSync和NonfairSync两种同步器,都继承者AQS。可以看到对应执行的是lock、release、和Sync的nonfairTryAcquire。从前面AQS源码知道release是在父类AQS中定义的方法,lock和nonfairTryAcquire是这个Sync中特定的方法,不是对父类对应方法的覆盖。

lock方法有对于FairSync和NoFairSync有两种不同的实现,对于非公平锁只要当前没有线程持有锁,就将锁给当前线程;而公平锁不能这么做,总是调用acquire方法来和其他线程一样公平的尝试获取锁。

比较公平锁机制和非公平锁机制的差别

仅仅在于如果当前没有线程持有锁,是优先把锁分配给当前线程(强调随机抢占,非公平锁),还是优先分配给等待队列中队首的线程公平锁)。

ReentrantLock总结:

ReentrantLock中定义的同步器分为公平的同步器和非公平的同步器。在该同步器中state状态位表示当前持有锁的线程的重入次数,有序队列中保存的是等待线程的顺序。

在获取锁时,通过覆盖AQS的tryAcquire(int arg)方法,如果没有线程持有锁则立即返回,并设置state为1;如果当前线程已经占有锁,则state加1;如果其他线程占有锁,则当前线程不可用;

释放锁时,覆盖了AQS的tryRelease(int arg),在该方法中主要作用是state状态位减少release个,表示释放锁,如果更新后的state为,表示当前线程释放锁,如果不为0,表示持有锁的当前线程重入次数减少。

既然有一个请求计数器和一个占有它的线程,即ReentrantLock实现了可重入锁。

(重点)注意:在ReentrantLock中,这些所有的lock操作都调用的AQS, AQS底层调用的是sun.misc.Unsafe对象(有大量的native方法和非native方法)进行同步操作,如unsafe.compareAndSetState

ReentrantLock 比 synchronized 功能更强大

主要体现

(1)ReentrantLock 具有公平策略的选择。

(2)ReentrantLock 可以在获取锁的时候,可有条件性地获取,可以设置等待时间,很有效地避免死锁。如 tryLock() 和 tryLock(long timeout, TimeUnit unit)

(3)ReentrantLock 可以获取锁的各种信息,用于监控锁的各种状态。

(4)ReentrantLock 可以灵活实现多路通知,即Condition的运用,将锁对象和等待-唤醒对象进行了分离(而synchronized锁对象和等待-唤醒都是同一个锁对象),可根据Condition对象唤醒指定的锁对象ReentrantLock(每个ReentrantLock对象可以对应一个Condition对象)。

锁的使用

(1)lock() 阻塞式地获取锁,只有在获取到锁后才处理interrupt信息

(2)lockInterruptibly() 阻塞式地获取锁,立即处理interrupt信息,并抛出异常

(3)tryLock() 尝试获取锁,不管成功失败,都立即返回true、false,注意的是即使已将此锁设置为使用公平排序策略,tryLock()仍然可以打开公平性去插队抢占。如果希望遵守此锁的公平设置,则使用 tryLock(0, TimeUnit.SECONDS),它几乎是等效的(也检测中断)。

(4)tryLock(long timeout, TimeUnit unit)在timeout时间内阻塞式地获取锁,成功返回true,超时返回false,同时立即处理interrupt信息,并抛出异常。

获取锁时遇到interrupt会抛异常:https://www.cnblogs.com/theRhyme/p/9444429.html

总结对照

本文主要侧重AQS的子类在各个同步工具类中的使用情况,其实也基本涵盖了这几个同步工具类的主要逻辑,但目标并不是对这几个同步工具类的代码进行详细解析。另外AQS本身的几个final方法,才是同步器的公共基础,也不是本文的主题,也未详细展开。其实写这篇文章的一个初始目的真的只是想列出如下表格,对比下AQS中的各个子类是怎么使用state的。

工具类 工具类作用 工具类加锁方法 工具类释放锁方法 Sync覆盖的方法 Sync非覆盖的重要方法 state的作用 锁类型 锁维护
Semaphore 控制同时访问某个特定资源的操作数量.(如限流的思想) acquire:每次请求一个许可都会导致计数器减少1,,一旦达到了0,新的许可请求线程将被挂起 release:每调用 添加一个许可,释放一个正在阻塞的获取者 tryAcquireShared tryReleaseShared   初始化总共的许可数(同时最多可以由几个线程访问) 共享锁 每一次请求acquire()一个许可都会导致计数器减少1,同样每次释放一个许可release()都会导致计数器增加1,一旦达到了0,新的许可请求线程将被挂起。
CountDownLatch 把一组线程全部关在外面,在某个状态时候放开。一种同步机制来保证一个或多个线程等待其他线程完成。(火箭发射) await:在计数器不为0时候阻塞调用线程,为0时候立即返回 countDown :计数递减 tryAcquireShared tryReleaseShared   维护一个计数器 共享锁 初始化一个计数,每次调用countDown方法计数递减,在计数递减到0之前,调用await的线程都会阻塞
ReentrantLock 标准的互斥操作,也就是一次只能有一个线程持有锁

lock:如果没有线程使用则立即返回,并设置state为1;如果当前线程已经占有锁,则state加1;如果其他线程占有锁,则当前线程不可用,等待

tryLock:如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false

unlock:尝试释放锁,如果当前线程占有锁则count减一,如果count为0则释放锁。如果占有线程不是当前线程,则抛异常 tryAcquire tryRelease nonfairTryAcquire state表示获得锁的线程对锁的重入次数。 排他锁 获取锁时,如果没有线程使用则立即返回,并设置state为1;如果当前线程已经占有锁,则state加1;如果其他线程占有锁,则当前线程不可用。释放锁时,在该方法中主要作用是state状态位减少release个,表示释放锁,如果更新后的state为0,表示当前线程释放锁,如果不为0,表示持有锁的当前线程重入数减少
ReentrantReadWriteLock 读写锁。允许多个读线程同时持有锁,但是只有一个写线程可以持有锁。写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁 ReadLock#lock :获取读锁 ReadLock#tryLock:尝试当前没有其他线程当前持有写锁时获取读锁 WriteLock#lock:获取写锁 WriteLock#tryLock:尝试当前没有其他线程持有写锁时,呼气写锁。 ReadLock#unlock:释放读锁 WriteLock#unlock:释放写锁 acquireShared releaseShared tryAcquire tryRelease tryReadLock tryWriteLock 高16位表示共享锁的数量,低16位表示独占锁的重入次数

读锁:共享

写锁:排他

对于共享锁,state是计数器的概念。一个共享锁就相对于一次计数器操作,一次获取共享锁相当于计数器加1,释放一个共享锁就相当于计数器减1;排他锁维护类似于可重入锁。
FutureTask 封装一个执行任务交给其他线程去执行,开始执行后可以被取消,可以查看执行结果,如果执行结果未完成则阻塞。 V get() run() set(V) cancel(boolean) tryAcquireShared tryReleaseShared innerGet innerRun() innerSet innerIsCancelled state状态位来存储执行状态RUNNING、RUN、CANCELLED 共享锁 获取执行结果的线程(可以有多个)一直阻塞,直到执行任务的线程执行完毕,或者执行任务被取消。

来源:

http://ifeve.com/abstractqueuedsynchronizer-use/#more-18899

AQS的子类在各个同步工具类中的使用情况的更多相关文章

  1. 同步工具类 CountDownLatch 和 CyclicBarrier

    在开发中,一些异步操作会明显加快执行速度带来更好的体验,但同时也增加了开发的复杂度,想了用好多线程,就必须从这些方面去了解 线程的 wait() notify() notifyall() 方法 线程异 ...

  2. 深入分析同步工具类之CountDownLatch

    概览: CountDownLatch又称闭锁,其作用是让一个或者多个线程挂起,直到其他的线程执行完后恢复挂起的线程,使其继续执行.内部维护着一个静态内部类Sync,该类继承AbstractQueued ...

  3. 深入分析同步工具类之AbstractQueuedSynchronizer

      概览: AQS(简称)依赖内部维护的一个FIFO(先进先出)队列,可以很好的实现阻塞.同步:volatile修饰的属性state,哪个线程先改变这个状态值,那么这个线程就获得了优先权,可以做任何事 ...

  4. 并发是个什么鬼之同步工具类CountDownLatch

    扯淡 写这篇文章,我先酝酿一下,实不相瞒,脱离底层太久了,更确切的情况是,真没曾认真研究过.就目前来说,很多框架包括工具类已经把实现封装的很深,你只需轻轻的调用一下API,便不费半点力气.以至于大家会 ...

  5. 《java并发编程实战》读书笔记4--基础构建模块,java中的同步容器类&并发容器类&同步工具类,消费者模式

    上一章说道委托是创建线程安全类的一个最有效策略,只需让现有的线程安全的类管理所有的状态即可.那么这章便说的是怎么利用java平台类库的并发基础构建模块呢? 5.1 同步容器类 包括Vector和Has ...

  6. Java多线程同步工具类之CountDownLatch

    在过去我们实现多线程同步的代码中,往往使用join().wait().notiyAll()等线程间通信的方式,随着JUC包的不断的完善,java为我们提供了丰富同步工具类,官方也鼓励我们使用工具类来实 ...

  7. JUC常用同步工具类——CountDownLatch,CyclicBarrier,Semaphore

    在 JUC 下包含了一些常用的同步工具类,今天就来详细介绍一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它们之间的区别. 一.CountDownLa ...

  8. 同步工具类—— CountDownLatch

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 CountDownLatch简介 CountDownLa ...

  9. Java核心知识点学习----线程同步工具类,CyclicBarrier学习

    线程同步工具类,CyclicBarrier日常开发较少涉及,这里只举一个例子,以做备注.N个人一块出去玩,相约去两个地方,CyclicBarrier的主要作用是等待所有人都汇合了,才往下一站出发. 1 ...

随机推荐

  1. 【SSH进阶之路】Spring的IOC逐层深入——为什么要使用IOC[实例讲解](二)

    上篇博客[SSH进阶之路]Spring简介,搭建Spring环境——轻量级容器框架(一),我们简单的介绍了Spring的基本概念,并且搭建了两个版本的Spring开发环境,但是我们剩下了Spring最 ...

  2. java容器——面试篇

    背景:java容器是面试中基础的基础,所以 有必要对着源码撸一遍. 进行了高度总结,首推: https://github.com/CyC2018/CS-Notes/blob/master/notes/ ...

  3. java中的Stream流

    java中的Stream流 说到Stream便容易想到I/O Stream,而实际上,谁规定"流"就一定是"IO流"呢?在Java 8中,得益于Lambda所带 ...

  4. CF1260D A Game with Traps

    http://codeforces.com/problemset/problem/1260/D 首先很明显可以想到二分答案,把能力值数组排个序就好. 考虑怎么check. 设当前二分值为w,即不能直接 ...

  5. LeetCode 541. 反转字符串 II(Reverse String II)

    541. 反转字符串 II 541. Reverse String II

  6. Java基础笔试练习(十二)

    1.(C#.JAVA)扩展方法能访问被扩展对象的public成员 A.能 B.不能 答案: A 解析: 翻译一下,子类方法是否能够访问父类中的public成员. 2.如果子类要调用父类的构造函数,则通 ...

  7. Select与Epoll的区别

      相同点: 都是IO多路转接,都是一个线程能同一时间等待一堆描述符 不同点: 1.select接口使用不方便,每次调用完select都需要重新设置fd_set,因为输入输出未分离,返回的fd_set ...

  8. logrus 剖析之滚动日志

    在实际开发过程中,为了节省磁盘,日志需要按照时间或者大小维度进行切割分成多分,归档过期的日志,删除久远的日志.这个就是在日常开发中经常遇见的日志滚动(log rotation) 那么在 logrus ...

  9. PB 报表数值列加%

  10. Java线程的等待与唤醒完整示例代码

    项目结构: 资源类: 输入线程:  输出线程: 测试: 人妖问题发生: 线程安全问题的解决方法: 调用Object的wait()和notify()方法时需注意:必须是锁对象方可调用,否则将抛出无效的监 ...