011-多线程-基础-基于AbstractQueuedSynchronizer自定义同步组件
一、概述
队列同步器AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架。
1.1、自定义独占锁同步组件
设计一个同步工具:该工具在同一时刻,只允许一个线程访问,超过一个线程的访问将被阻塞,我们将这个同步器命名为Mutex。
首先,确定访问模式,MutexLock在同一时刻只支持一个线程的访问,这显然是独占式访问,因此,需要使用AQS提供的acquire(int)方法以及release(int)等方法,这就要求Mutex必须重写tryAcquire(int)和tryRelease(int)方法,才能保证AQS的独占式同步状态的获取与释放方法得以执行。
其次,定义同步状态(或者资源数),MutexLock在同一时刻只支持一个线程的访问,我们用0表示同步状态未被获取,用其他数值表示已被获取,这里,我们就用1表示,当一个线程进行获取,通过CAS操作将同步状态设置为1,该线程释放时,将同步状态置为0,这样就实现了一个简单的独占式同步组件。MutexLock源码如下:
package com.github.bjlhx15.common.thread.juc.collection; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock; public class MutexLock implements Lock {
private final Sync sync = new Sync(); private static final class Sync extends AbstractQueuedSynchronizer {
// 当状态为0的时候获取锁,通过CAS将状态置为1
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
} return false;
} // 释放锁,将状态置为0
public boolean tryRelease(int releases) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
} setExclusiveOwnerThread(null);
setState(0);
return true;
}
} @Override
public void lock() {
sync.acquire(1);
} @Override
public void unlock() {
sync.release(1);
} @Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
} @Override
public Condition newCondition() {
return null;
} @Override
public boolean tryLock() {
return sync.tryAcquire(1);
} @Override
public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
return sync.tryAcquireNanos(1, arg1.toNanos(arg0));
}
}
定义了一个静态内部类,该内部类继承了同步器并实现了独占式获取和释放锁的同步状态操作。在tryAcquire(int)方法中,如果通过CAS设置成功,则代表获取到了同步状态,而在tryRelease(int)方法中,只是将同步状态置为0,而不用CAS操作
测试
public class MutexLockTest {
private final static MutexLock mutex = new MutexLock();
private static final class Worker extends Thread {
@Override
public void run() {
super.run();
while (true) {
// 获取锁
mutex.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
mutex.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// 启动10个线程
for (int i = 0; i < 10; i++) {
Worker worker = new Worker();
worker.setDaemon(true);
worker.start();
}
Thread.sleep(10000);
}
}
结果
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
运行过程中可以看到,每次只有一个线程输出,也就是同一时刻只有一个线程获取了锁,这表明Mutex能够完成独占锁的功能,但是Mutex还存在如下的问题:
不可重入:重入是指任意线程在获取锁之后能够再次获取该锁而不会被锁所阻塞,在上述代码中,当一个调用mutex.lock()获取锁之后,如果再次调用mutex.lock(),那么该线程将会被自己阻塞,因为在Mutex在实现tryAcquire(int)方法时,没有考虑占有锁的线程再次获取锁的情况,而在线程再次调用tryAcquire(int)方法时返回了false,导致线程被阻塞。
非公平:通过程序输出我们可以发现,在10秒之内,获取锁的线程只有Thread-0和Thread-1,而其他的线程都只能阻塞,这很容易产生饥饿现象。
这两个问题在JDK提供的重入锁ReentrantLock中,都得到了解决,我们后续会对ReentrantLock进行详解。
1.2、自定义共享锁同步组件
设计一个同步工具:该工具在同一时刻,只允许最多两个线程访问,超过两个线程的访问将被阻塞,我们将这个同步器命名为TwinsLock。
首先,确定访问模式,TwinsLock在同一时刻支持多个线程的访问,这显然是共享式访问,因此,需要使用AQS提供的acquireShared(int)方法以及releaseShared(int)等方法,这就要求TwinsLock必须重写tryAcquireShared(int)和tryReleaseShared(int)方法,才能保证AQS的共享式同步状态的获取与释放方法得以执行。
其次,定义同步状态(或者资源数),TwinsLock在同一时刻允许最多两个线程的访问,我们可以将同步状态初始值设置为2,表示可用的同步资源数目,当一个线程进行获取,status减1,该线程释放时,status加1,状态的合法范围为0、1、2,其中0表示当前已经有两个线程获取了同步资源,此时再有其他线程获取同步状态,该线程只能被阻塞。同步状态的变更都通过CAS操作做原子性保障。TwinsLock源码如下:
public class TwinsLock implements Lock {
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer {
public Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("count must larger than zero.");
}
setState(count);
}
public int tryAcquireShared(int reduceCount) {
for (;;) {
int currentCount = getState();
int newCount = currentCount - reduceCount;
if (newCount < 0 || compareAndSetState(currentCount, newCount)) {
return newCount;
}
}
}
public boolean tryReleaseShared(int returnCount) {
for (;;) {
int currentCount = getState();
int newCount = currentCount + returnCount;
if (compareAndSetState(currentCount, newCount)) {
return true;
}
}
}
}
@Override
public void lock() {
sync.acquireShared(1);
}
@Override
public void unlock() {
sync.releaseShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public Condition newCondition() {
return null;
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
return false;
}
}
TwinsLock实现了Lock接口,提供了面向使用者的接口,使用者调用lock()方法获取锁,随后调用unlock()方法释放锁,而同一时刻只能有最多两个线程获取锁。TwinsLock包含了一个自定义同步器Sync,而该同步器面向线程访问和同步状态控制。线程获取锁时,同步器会先计算出获取后的同步状态,然后通过CAS确保状态的正确设置,当tryAcquireShared(int)方法返回值大于等于0时,表示线程获取了同步状态,对于上层TwinsLock而言,表示当前线程获取了锁。
下面编写了一个程序来对TwinsLock进行验证:
public class TwinsLockTest {
private final static TwinsLock twinsLock = new TwinsLock();
private static final class Worker extends Thread {
@Override
public void run() {
super.run();
while (true) {
twinsLock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
twinsLock.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// 启动10个线程
for (int i = 0; i < 10; i++) {
Worker worker = new Worker();
worker.setDaemon(true);
worker.start();
}
Thread.sleep(10000);
}
}
运行结果(不唯一):
Thread-1
Thread-0
Thread-3
Thread-2
Thread-2
Thread-3
Thread-2
Thread-3
Thread-2
Thread-3
运行过程中可以看到,线程的名称都是成对输出,也就是同一时刻有两个线程获取了锁,这表明TwinsLock可以按照预期正常工作。但是,TwinsLock同样也存在重入和非公平的问题。
011-多线程-基础-基于AbstractQueuedSynchronizer自定义同步组件的更多相关文章
- 学习JUC源码(2)——自定义同步组件
前言 在之前的博文(学习JUC源码(1)--AQS同步队列(源码分析结合图文理解))中,已经介绍了AQS同步队列的相关原理与概念,这里为了再加深理解ReentranLock等源码,模仿构造同步组件的基 ...
- java实现自定义同步组件的过程
实现同步组件twinsLock:可以允许两个线程同时获取到锁,多出的其它线程将被阻塞. 以下是自定义的同步组件类,一般我们将自定义同步器Sync定义为同步组件TwinsLock的静态内部类. 实现同步 ...
- Java显式锁学习总结之二:使用AbstractQueuedSynchronizer构建同步组件
Jdk1.5中包含了并发大神Doug Lea写的并发工具包java.util.concurrent,这个工具包中包含了显示锁和其他的实用同步组件.Doug Lea在构建锁和组件的时候,大多是以队列同步 ...
- salesforce lightning零基础学习(十三) 自定义Lookup组件(Single & Multiple)
上一篇简单的介绍了自定义的Lookup单选的组件,功能为通过引用组件Attribute传递相关的sObject Name,捕捉用户输入的信息,从而实现搜索的功能. 我们做项目的时候,可能要从多个表中获 ...
- AQS 原理以及 AQS 同步组件总结
1 AQS 简单介绍 AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面. AQS 是一个用来构建锁和同步 ...
- 构建锁与同步组件的基石AQS:深入AQS的实现原理与源码分析
Java并发包(JUC)中提供了很多并发工具,这其中,很多我们耳熟能详的并发工具,譬如ReentrangLock.Semaphore,它们的实现都用到了一个共同的基类--AbstractQueuedS ...
- 源码分析:同步基础框架——AbstractQueuedSynchronizer(AQS)
简介 AQS 全称是 AbstractQueuedSynchronizer,位于java.util.concurrent.locks 包下面,AQS 提供了一个基于FIFO的队列和维护了一个状态sta ...
- 基础篇:JAVA原子组件和同步组件
前言 在使用多线程并发编程的时,经常会遇到对共享变量修改操作.此时我们可以选择ConcurrentHashMap,ConcurrentLinkedQueue来进行安全地存储数据.但如果单单是涉及状态的 ...
- Java 多线程基础(五)线程同步
Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...
随机推荐
- Airtest---UI自动化测试项目
Airtest Project是网易游戏团队新开源出来的一款用于UI自动化测试的项目. testerhome中的文档介绍:https://testerhome.com/topics/12486 官方链 ...
- Mybatis-Generator逆向工程,复杂策略(Criteria拼接条件)
基于上一篇修改 1.Generator配置文件修改,将targetRuntime改为MyBatis3 2.项目结构目录 这个xxxExample就是拼接条件用的 3.测试代码 注释写的很详细 publ ...
- PHP搭建大文件切割分块上传功能示例
转载:https://www.jb51.net/article/101931.htm 背景 在网站开发中,文件上传是很常见的一个功能.相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示“该文件 ...
- 005_硬件基础电路_PCB安规设计规范
包含两个文件:讲解pcb绘制过程中的安规要求 002_2_PCB安规设计规范(原创-绝对经典全面-玩转高压PCB设计)总结 002_3_电气间隙和爬电距离规定 链接:https://pan.baidu ...
- 001_软件安装之《MATLAB2016安装》
测试电脑:win7 64位操作系统 下载地址: 链接:https://pan.baidu.com/s/1xkyhF6pdkx_kZiNjFireZw 密码:mvpp 链接:https://pa ...
- centos6.5 安装163yum源
1.下载yum源 http://mirrors.163.com/.help/CentOS6-Base-163.repo 2.把下载好的yum源放到/etc/yum.repos.d下 mv CentOS ...
- mongodb存储引擎WiredTiger
MongoDB3.2后默认采用WiredTiger存储引擎. 组成 WiredTiger由三部分组成: Mongos: 负责查询请求的路由和对ShardServer的管理: ConfigServe ...
- Postgresql 日志相关
目录日志种类作用总结配置文件中与日志相关的配置日志种类 PostgreSQL有3种日志 pg_log(数据库运行日志) 内容可读 默认关闭的,需要设置参数启动pg_xlog(WAL 日志,即 ...
- Linux操作系统常用命令合集——第六篇-压缩和归档操作(16个命令)
1.gzip [命令作用] 压缩和解压缩文件 gzip/guzip/zcat zcat:不显式展开的前提下查看文本文件内容 zdiff/zgrep/zless/zmore [命令语法] gzip ...
- 斜率dp的模板总结
#include<cstdio> #include<algorithm> using namespace std; long long sumt[40005],sum[4000 ...