一、包的结构层次

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

二、Lock和synchronized的比较

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

通常Lock使用的形式如下:

Lock lock=new ReentrantLock();
lock.lock();
try { } finally {
lock.unlock();
}

需要注意的是: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

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

protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

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

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}

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

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

会调用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

class Mutex implements Lock, java.io.Serializable {

    // Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
// Reports whether in locked state
protected boolean isHeldExclusively() {
return getState() == ;
} // Acquires the lock if state is zero
public boolean tryAcquire(int acquires) {
assert acquires == ; // Otherwise unused
if (compareAndSetState(, )) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
} // Releases the lock by setting state to zero
protected boolean tryRelease(int releases) {
assert releases == ; // Otherwise unused
if (getState() == )
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState();
return true;
} // Provides a Condition
Condition newCondition() {
return new ConditionObject();
} // Deserializes properly
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(); // reset to unlocked state
}
} // The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync(); public void lock() {
sync.acquire();
} public boolean tryLock() {
return sync.tryAcquire();
} public void unlock() {
sync.release();
} public Condition newCondition() {
return sync.newCondition();
} public boolean isLocked() {
return sync.isHeldExclusively();
} public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
} public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly();
} public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(, unit.toNanos(timeout));
}
}
package passtra;

public class MutextDemo {
private static Mutex mutex = new Mutex(); public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
mutex.lock();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mutex.unlock();
}
});
thread.start();
}
}
}

这个例子来源于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. 哇咔咔干货来啦:PowerJob 原理剖析之 Akka Toolkit

    本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列. Akka is a toolkit for bui ...

  2. 删除GIT中的.DS_Store

    转载自:https://www.jianshu.com/p/fdaa8be7f6c3 .DS_Store 是什么 使用 Mac 的用户可能会注意到,系统经常会自动在每个目录生成一个隐藏的 .DS_St ...

  3. PHP date_diff() 函数

    ------------恢复内容开始------------ 实例 计算两个日期间的差值: <?php$date1=date_create("2013-03-15");$da ...

  4. 2019 HL SC day1

    今天讲的是图论大体上分为:有向图的强连通分量,有向图的完全图:竞赛图,无向图的的割点,割边,点双联通分量,变双联通分量以及圆方树 2-sat问题 支配树等等. 大体上都知道是些什么东西 但是仍需要写一 ...

  5. PyCharm2020激活破解教程

    本文内容皆为作者原创,如需转载,请注明出处:https://www.cnblogs.com/xuexianqi/p/12767075.html 免责声明:本方法只做学习研究之用,不得用于商业用途 若经 ...

  6. ORACLE不完成恢复ORA-00392,ORA-00312,ORA-00349

    背景: 进行测试库不完全恢复,log_file_name_convert没调整好.rac-asm至单实例-文件系统,recover完成后,mount状态的database  执行 alter data ...

  7. 基于Python+Requests+Pytest+YAML+Allure实现接口自动化

    本项目实现接口自动化的技术选型:Python+Requests+Pytest+YAML+Allure ,主要是针对之前开发的一个接口项目来进行学习,通过 Python+Requests 来发送和处理H ...

  8. 014_go语言中的变参函数

    代码演示 package main import "fmt" func sum(nums ...int) { fmt.Print(nums, " ") toto ...

  9. JS解密入门——有道翻译

    JS解密入门——有道翻译 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这 ...

  10. JS学习第二天

    数组: var arr1=[2,5,6];    定义时直接给数组元素赋值 var arr2=[];   定义一个空数组 var arr3=new Array();     定义一个空数组并通过索引来 ...