Java并发---concurrent包
一、包的结构层次
其中包含了两个子包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包的更多相关文章
- 【并发编程】【JDK源码】JDK的(J.U.C)java.util.concurrent包结构
本文从JDK源码包中截取出concurrent包的所有类,对该包整体结构进行一个概述. 在JDK1.5之前,Java中要进行并发编程时,通常需要由程序员独立完成代码实现.当然也有一些开源的框架提供了这 ...
- 高并发编程基础(java.util.concurrent包常见类基础)
JDK5中添加了新的java.util.concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全性,所以这种方法 ...
- 线程并发线程安全介绍及java.util.concurrent包下类介绍
线程Thread,在Java开发中多线程是必不可少的,但是真正能用好的并不多! 首先开启一个线程三种方式 ①new Thread(Runnable).start() ②thread.start(); ...
- 深入理解java:2.3. 并发编程 java.util.concurrent包
JUC java.util.concurrent包, 这个包是从JDK1.5开始引入的,在此之前,这个包独立存在着,它是由Doug Lea开发的,名字叫backport-util-concurrent ...
- java.util.concurrent包
在JavaSE5中,JUC(java.util.concurrent)包出现了 在java.util.concurrent包及其子包中,有了很多好玩的新东西: 1.执行器的概念和线程池的实现.Exec ...
- java.util.concurrent包API学习笔记
newFixedThreadPool 创建一个固定大小的线程池. shutdown():用于关闭启动线程,如果不调用该语句,jvm不会关闭. awaitTermination():用于等待子线程结束, ...
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- Java:concurrent包下面的Map接口框架图(ConcurrentMap接口、ConcurrentHashMap实现类)
Java集合大致可分为Set.List和Map三种体系,其中Set代表无序.不可重复的集合:List代表有序.重复的集合:而Map则代表具有映射关系的集合.Java 5之后,增加了Queue体系集合, ...
- Java:concurrent包下面的Collection接口框架图( CopyOnWriteArraySet, CopyOnWriteArrayList,ConcurrentLinkedQueue,BlockingQueue)
Java集合大致可分为Set.List和Map三种体系,其中Set代表无序.不可重复的集合:List代表有序.重复的集合:而Map则代表具有映射关系的集合.Java 5之后,增加了Queue体系集合, ...
随机推荐
- ParallelsDesktop下Kali安装
1. 安装镜像 镜像百度云:链接:https://pan.baidu.com/s/1TFXwmvehDdO-cwtU__TmqQ 密码:f3ow Kali官网 ,需要最新或者想下载其他版本去官网下载吧 ...
- 机器学习笔记簿 降维篇 PCA 01
降维是机器学习中十分重要的部分,降维就是通过一个特定的映射(可以是线性的或非线性的)将高维数据转换为低维数据,从而达到一些特定的效果,所以降维算法最重要的就是找到这一个映射.主成分分析(Princip ...
- MapReduce之Combiner合并
Combiner是MR程序中Mapper和Reducer之外的一种组件(本质是一个Reducer类) Combinr组件的父类就是Reducer Conbimer只有在驱动类里设置了之后,才会运行 C ...
- PHP is_dir() 函数
定义和用法 is_dir() 函数检查指定的文件是否是一个目录. 如果目录存在,该函数返回 TRUE. 语法 is_dir(file) 参数 描述 file 必需.规定要检查的文件. 提示和注释 注释 ...
- 4.17 斐波那契数列 K维斐波那契数列 矩阵乘法 构造
一道矩阵乘法的神题 早上的时候我开挂了 想了2h想出来了. 关于这道题我推了很多矩阵 最终推出两个核心矩阵 发现这两个矩阵放在一起做快速幂就行了. 当k==1时 显然的矩阵乘法 多开一个位置维护前缀和 ...
- CF R 635 div2 1337D Xenia and Colorful Gems 贪心 二分 双指针
LINK:Xenia and Colorful Gems 考试的时候没想到一个很好的做法. 赛后也有一个想法. 可以考虑答案的样子 x,y,z 可以发现 一共有 x<=y<=z,z< ...
- [NewLife.Net]单机400万长连接压力测试
目标 对网络库NewLife.Net进行单机百万级长连接测试,并持续收发数据,检测网络库稳定性. [2020年8月1日晚上22点] 先上源码:https://github.com/NewLifeX/N ...
- day22:面向对象封装对象操作&类操作&面向对象删除操作
面向对象程序开发 1.类的三种定义方式 class MyClass: pass class MyClass(): #(推荐) pass class MyClass(object): # object类 ...
- ~/.ssh/目录找不到解决方法
执行 cd ~/.ssh发现.ssh目录找不到 原因是因为没有用root用户ssh登录过,执行一下ssh操作就会自动生成了
- Flink的状态编程和容错机制(四)
一.状态编程 Flink 内置的很多算子,数据源 source,数据存储 sink 都是有状态的,流中的数据都是 buffer records,会保存一定的元素或者元数据.例如 : ProcessWi ...