一、包的结构层次

其中包含了两个子包atomic和locks,另外字concurrent下的阻塞队列以及executor,这些就是concurrent包中的精华。而这些类的实现主要是依赖于volatile和CAS,从整体上看concurrent包的整体实现图如下:

二、Lock和synchronized的比较

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源。在Lock接口出现之前,Java主要是靠synchronized关键字实现锁功能的,而Java1.5之后,并发包增加了Lock接口,它提供了与synchronized一样的锁功能。虽然它失去了想synchronized关键字隐式加锁解锁的便捷性,单却拥有了获取锁和释放锁的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性

通常Lock使用的形式如下:

  1. Lock lock=new ReentrantLock();
  2. lock.lock();
  3. try {
  4.  
  5. } finally {
  6. lock.unlock();
  7. }

需要注意的是:synchronized同步块执行完成活着遇到异常会自动释放锁,而lock必须调用unlock()释放锁,因此必须在finally中释放锁。

三、Lock接口API的介绍

看看Lock定义了哪些方法:

void lock();获取锁

void lockInterruptibly() throws InterruptedException;获取所的过程能够响应中断

boolean tryLock();非阻塞式响应中断能立即返回,获取锁返回true,反之返回false;

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;超时获取锁,在超时内或者中断的情况下能够获取锁

Condition newCondition();获取与lock绑定的等待通知组件,当前线程必须获得了锁才能进行等待,进行等待时会先释放锁,当再次获取锁时才能从等待中返回;

void unlock();释放锁

那么在locks包下有哪些类实现了改接口?

ReentrantLock

  1. public class ReentrantLock implements Lock, java.io.Serializable

很显然ReentrantLock实现了lock接口,当你查看源码是会发现ReentrantLock并没有多少代码,另外一个很明显的特点是:基本上所有的方法的实现实际上都是调用了其静态内部类Sync中的方法,而Sync类继承了AbstractQueuedSynchronizer(AQS)。可以看出ReentrantLock关键核心在于对队列同步器AbstractQueuedSynchronizer的理解。

四、了解队列同步器AQS(AbstractQueuedSynchronizer)

关于AQS在源码中有十分具体的解释

同步器是用来构建锁和其他同步组件的基础框架,它的实现主要依赖一个int成员变量来表示同步状态以及通过一个FIFO队列构成等待队列。它的子类必须重写AQS的几个protected修饰的用来改变同步状态的方法,其他方法主要是实现了排队和阻塞机制。状态的更新使用getState,setState以及compareAndSetState这三个方法。

子类被推荐定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态的获取和释放方法来供自定义同步组件的使用,同步器既支持独占式获取同步状态,也支持共享式获取同步状态,这样就可以方便的实现不同类型的同步组件。

同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者的关系:锁是面向使用者,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态的管理,线程的排队,等待和唤醒等底层操作。锁和同步器很好的隔离了使用者和实现者所需要关注的领域。

五、AQS的设计模式

AQS的设计模式是使用模板方法设计模式,它将一些方法开放给子类进行重写,而同步器给同步组件所提供模板方法又会重新被子类重写的方法。

举个例子,AQS中需要重写的方法tryAcquire

  1. protected boolean tryAcquire(int arg) {
  2. throw new UnsupportedOperationException();
  3. }

ReentrantLock中NonfairSync(继承AQS)会重写该方法为:

  1. protected final boolean tryAcquire(int acquires) {
  2. return nonfairTryAcquire(acquires);
  3. }

而AQS中的模板方法acquire():

  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg) &&
  3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4. selfInterrupt();
  5. }

会调用tryAcquire方法,而此时当继承AQS的NonfairSync调用模板方法acquire时就会调用已经被NonfairSync重写的tryAcquire方法。这就是使用AQS的方式。

同步组件(这里不仅仅指锁,还包括CountDownLatch等)的实现依赖于同步器AQS,在同步组件实现中,使用AQS的方式被推荐定义继承AQS的静态内部类;

AQS采用模板方法进行设计,AQS的protected修饰的方法需要由继承AQS的子类进行重写实现,当调用AQS的子类的方法是就会调用被重写的方法;

AQS负责同步状态的管理,线程的排队,等待和唤醒这些底层操作,而Lock等同步组件主要专注于实现同步语义;

在重写AQS的方式时,使用AQS提供的getState(),setState()以及compareAndSetState()方法进行修改同步状态.

AQS可重写的方法如下图:

在实现同步组件是AQS提供的模板方法如下图

 AQS提供的模板方法可以分为3类:

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

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

查询同步队列中等待线程情况

同步组件通过AQS提供的模板方法实现自己的同步语义。

六、AQS的使用example

  1. class Mutex implements Lock, java.io.Serializable {
  2.  
  3. // Our internal helper class
  4. private static class Sync extends AbstractQueuedSynchronizer {
  5. // Reports whether in locked state
  6. protected boolean isHeldExclusively() {
  7. return getState() == ;
  8. }
  9.  
  10. // Acquires the lock if state is zero
  11. public boolean tryAcquire(int acquires) {
  12. assert acquires == ; // Otherwise unused
  13. if (compareAndSetState(, )) {
  14. setExclusiveOwnerThread(Thread.currentThread());
  15. return true;
  16. }
  17. return false;
  18. }
  19.  
  20. // Releases the lock by setting state to zero
  21. protected boolean tryRelease(int releases) {
  22. assert releases == ; // Otherwise unused
  23. if (getState() == )
  24. throw new IllegalMonitorStateException();
  25. setExclusiveOwnerThread(null);
  26. setState();
  27. return true;
  28. }
  29.  
  30. // Provides a Condition
  31. Condition newCondition() {
  32. return new ConditionObject();
  33. }
  34.  
  35. // Deserializes properly
  36. private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
  37. s.defaultReadObject();
  38. setState(); // reset to unlocked state
  39. }
  40. }
  41.  
  42. // The sync object does all the hard work. We just forward to it.
  43. private final Sync sync = new Sync();
  44.  
  45. public void lock() {
  46. sync.acquire();
  47. }
  48.  
  49. public boolean tryLock() {
  50. return sync.tryAcquire();
  51. }
  52.  
  53. public void unlock() {
  54. sync.release();
  55. }
  56.  
  57. public Condition newCondition() {
  58. return sync.newCondition();
  59. }
  60.  
  61. public boolean isLocked() {
  62. return sync.isHeldExclusively();
  63. }
  64.  
  65. public boolean hasQueuedThreads() {
  66. return sync.hasQueuedThreads();
  67. }
  68.  
  69. public void lockInterruptibly() throws InterruptedException {
  70. sync.acquireInterruptibly();
  71. }
  72.  
  73. public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
  74. return sync.tryAcquireNanos(, unit.toNanos(timeout));
  75. }
  76. }
  1. package passtra;
  2.  
  3. public class MutextDemo {
  4. private static Mutex mutex = new Mutex();
  5.  
  6. public static void main(String[] args) {
  7. for (int i = 0; i < 10; i++) {
  8. Thread thread = new Thread(() -> {
  9. mutex.lock();
  10. try {
  11. Thread.sleep(3000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. } finally {
  15. mutex.unlock();
  16. }
  17. });
  18. thread.start();
  19. }
  20. }
  21. }

这个例子来源于AQS源码的example,执行情况:

上面的例子实现了独占锁的语义,在同一个时刻只允许一个线程占有锁。 MutextDemo新建10个线程,分别睡眠3s。从执行情况也可以看出当前Thread-6正在执行占有锁而其他线程7/8处于WAIT状态。按照推荐的方式,Mutext定义了一个集成AQS的静态内部类Sync,并且重写了AQS的tryAcquire等等方法,而对state的更新也是利用了getState,setState,compareAndSetStaste这三个方法。在实现lock接口中方法也是调用AQS提供的模板方法(因为Sync继承了AQS)。

从这个例子就可以很清楚的看出来,在同步组件的实现上主要利用了AQS,而AQS“屏蔽”了同步状态的修改,线程排队等底层实现,通过AQS的模板方法可以很方便的给同步组件的实现警醒调用。而针对用户来说,只需要调用同步组件提供的方法来实现并发编程即可,同时在新建一个同步组件时需要把握的两个关键点是:

实现同步组件时推荐定义继承AQS的静态内部类,并重写需要的proyected修饰的方法;

同步组件语义的实现依赖于AQS的模板方法,而AQS模板方法有依赖于AQS的子类重写的方法。

通俗的说,因为AQS整体设计思路采用模板方法设计模式,同步组件以及AQS的功能实际上分别切换成各自的两部分:

同步组件实现者的角度:

通过可重新写的方法:

独占式:

tryAcquire()(独占式获取同步状态);

tryRelease()(独占式释放同步状态)

共享式:

tryAcquireShared()(共享式获取同步状态)

tryReleaseShared()(共享式释放同步状态

告诉AQS怎么判断当前同步状态是否成功获取或者是否成功释放。

同步组件专注于对当前同步状态的逻辑判断,从而实现自己的同步语义。这句话比较抽象,举个例子,上面的Mutex例子中通过tryAcquire方法实现自己的同步语义,在该方法中如果当前同步状态为0(即该同步组件没有被任何线程获取),当前线程可以获取同时将状态更改为1返回true,否则,该组件只能在同一时刻被线程占用,mutex专注于获取释放的逻辑来实现自己想要表达的同步语义。

AQS角度

而对AQS来说,只需要同步组件返回的true和false即可,因为AQS会对true和fals会有不同的操作,true会认为当前线程获取同步组件成功直接返回,而false的话AQS会将当前献策还给你插入同步队列等一系列的方法。

总的来说,同步组件通过重写AQS的方法实现自己想要表达的同步语义,而AQS只需要同步组件表达true和false即可,AQS会针对true和false不同的情况做不同的处理。

Java并发---concurrent包的更多相关文章

  1. 【并发编程】【JDK源码】JDK的(J.U.C)java.util.concurrent包结构

    本文从JDK源码包中截取出concurrent包的所有类,对该包整体结构进行一个概述. 在JDK1.5之前,Java中要进行并发编程时,通常需要由程序员独立完成代码实现.当然也有一些开源的框架提供了这 ...

  2. 高并发编程基础(java.util.concurrent包常见类基础)

    JDK5中添加了新的java.util.concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全性,所以这种方法 ...

  3. 线程并发线程安全介绍及java.util.concurrent包下类介绍

    线程Thread,在Java开发中多线程是必不可少的,但是真正能用好的并不多! 首先开启一个线程三种方式 ①new Thread(Runnable).start() ②thread.start(); ...

  4. 深入理解java:2.3. 并发编程 java.util.concurrent包

    JUC java.util.concurrent包, 这个包是从JDK1.5开始引入的,在此之前,这个包独立存在着,它是由Doug Lea开发的,名字叫backport-util-concurrent ...

  5. java.util.concurrent包

    在JavaSE5中,JUC(java.util.concurrent)包出现了 在java.util.concurrent包及其子包中,有了很多好玩的新东西: 1.执行器的概念和线程池的实现.Exec ...

  6. java.util.concurrent包API学习笔记

    newFixedThreadPool 创建一个固定大小的线程池. shutdown():用于关闭启动线程,如果不调用该语句,jvm不会关闭. awaitTermination():用于等待子线程结束, ...

  7. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  8. Java:concurrent包下面的Map接口框架图(ConcurrentMap接口、ConcurrentHashMap实现类)

    Java集合大致可分为Set.List和Map三种体系,其中Set代表无序.不可重复的集合:List代表有序.重复的集合:而Map则代表具有映射关系的集合.Java 5之后,增加了Queue体系集合, ...

  9. Java:concurrent包下面的Collection接口框架图( CopyOnWriteArraySet, CopyOnWriteArrayList,ConcurrentLinkedQueue,BlockingQueue)

    Java集合大致可分为Set.List和Map三种体系,其中Set代表无序.不可重复的集合:List代表有序.重复的集合:而Map则代表具有映射关系的集合.Java 5之后,增加了Queue体系集合, ...

随机推荐

  1. scrapyd 部署

    步骤 1 pip install scrapyd pip install scrapy-client 步骤 2 修改 scrapy.cfg [deploy:targetName]url = http: ...

  2. Django学习路24_乘法和除法

    urls 中 url(r'getnum',views.getnum) views.py 中添加对应的函数 def getnum(request): num = 5 context_num = { 'n ...

  3. MySQL在同一个表上,删除查询出来的结果

    背景 有一个程序员员工表(code_user),包含用户id.姓名.掌握的语言. 表数据如下: +---------+-----------+----------+ | user_id | user_ ...

  4. PHP mkdir() 函数

    定义和用法 mkdir() 函数创建目录. 如果成功该函数返回 TRUE,如果失败则返回 FALSE. 语法 mkdir(path,mode,recursive,context) 参数 描述 path ...

  5. 排序HEOI2016/TJOI2016 二分+线段树判定

    LINK:排序 此题甚好我一点思路都没有要是我当时省选此题除了模拟我恐怕想不到还可以二分 还可以线段树... 有点ex 不太好写 考虑 暴力显然每次给出询问我们都是可以直接sort的 无视地形无视一切 ...

  6. ubuntu16.04下chrome安装flash插件

    最近自己的ubuntu安装了最新的chrome54版本,发现视频无法播放,提示flash版本过期,原来最新的chrome已经不内置flash插件了,需要自己安装. 方法/步骤 1.安装chrome打开 ...

  7. Android Spinner的简单用法。

    今天学到的是spinner,就是下拉列表,这可不是ExpandListView哈. 闲话不解释.这是控件,所以先上布局:就不上线性布局了,基本上可以总结出,控件都得在布局里写,写之前嵌个布局就行. & ...

  8. 07-NABCD项目分析

    时    间:2020.3.31 参加人员:向瑜.赵常恒.刘志霄 讨论记录内容: NABCD模型 ·N(need)-向瑜 你的创意解决了用户的什么需求? 1. 随时随地记录个人收支的明细,清楚明白的知 ...

  9. js 排他思想案例

    <!-- 排他思想 --> <button>按钮1</button> <button>按钮2</button> <button> ...

  10. C#LeetCode刷题之#237-删除链表中的节点(Delete Node in a Linked List)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3832 访问. 请编写一个函数,使其可以删除某个链表中给定的(非末 ...