前言

  在之前的博文(学习JUC源码(1)——AQS同步队列(源码分析结合图文理解))中,已经介绍了AQS同步队列的相关原理与概念,这里为了再加深理解ReentranLock等源码,模仿构造同步组件的基本模式,编写不可重复的互斥锁Mutex与指定共享线程数量的共享锁。MySharedLock。

  主要参考资料《Java并发编程艺术》(有需要的小伙伴可以找我,我这里只有电子PDF)同时结合ReentranLock、AQS等源码。


一、构造同步组件的模式

丛概念方层面,在中,我们知道锁与同步器的相关概念:

  • 同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义;
  • 锁是面向使用者的,提供锁交互的实现;
  • 同步器是面向锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程排队、等待/唤醒等底层操作。

从代码层面,同步器是基于模板模式实现的,可以通过可重写的方法中的随便一个窥探:

  /**
* 模板方法:
* protected关键字
* 没有任何实现
* @param arg
* @return
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

也就是需要进行以下几步:

1)继承同步器重写指定方法(idea中extends AQS点击快捷键ctrl+O即可显示)

  • tryAcquire(int arg):独占式获取同步状态;
  • tryRelease(int arg):独占式释放同步状态;
  • tryAcquireShared(int arg):共享式获取同步状态,返回大于0的值表示获取成功,否则失败
  • tryReleaseShared(int arg):共享式释放锁
  • isHeldExclusively():当前线程是否在独占模式下被线程占用,一般该方法表示是否被当前线程占用

2)随后将同步器组合在自定义同步组件的实现中,即定义内部类Syn继承AQS,在Syn中重写AQS方法:

public class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
if (compareAndSetState(0, 1)) {
// 获取成功之后,当前线程是该锁的持有者,不需要再可重入数
setExclusiveOwnerThread(current);
return true;
}
return false;
} @Override
protected boolean tryRelease(int arg) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
} @Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 返回Condition,每个Condition都包含了一个队列
Condition newCondition() {
return new ConditionObject();
}
}

3)最后调用同步器提供的模板方法,即同步组件类实现Lock方法之后,在lock/unlock方法中调用内部类Syn的方法acquire(int arg)等方法

public class Mutex implements Lock {

   ........
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
........ }

具体请看下面的实践部分

二、互斥不可重入锁

在我之前写过的博文中(详解Java锁的升级与对比(1)——锁的分类与细节(结合部分源码))介绍可重入锁与不可重入锁的区别时,就写到JUC中没有不可重入锁的具体实现,但是可以类比,现在呢,我们可以做到实现了,具体看下面代码,模式完全符合依赖Lock与AQS构造同步组件模式。

(1)Mutex代码实现(核心关键实现已经在代码中注释)

public class Mutex implements Lock {

    private final Sync sync = new Sync();
public class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
if (compareAndSetState(0, 1)) {
// 获取成功之后,当前线程是该锁的持有者,不需要再可重入数
setExclusiveOwnerThread(current);
return true;
}
return false;
} @Override
protected boolean tryRelease(int arg) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
} @Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 返回Condition,每个Condition都包含了一个队列
Condition newCondition() {
return new ConditionObject();
}
} @Override
public void lock() {
sync.acquire(1);
}
@Override
public void unlock() {
sync.release(1);
} @Override
public void lockInterruptibly() throws InterruptedException { } @Override
public boolean tryLock() {
return false;
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
} @Override
public Condition newCondition() {
return null;
}
}

其中核心代码就是重写的两个方法:

  • tryAcquire(int arg)方法:主要是设置同独占式更新同步状态,CAS实现state+1
  • tryRelease(int arg)方法:独占式释放同步状态,释放锁持有

(2)测试Demo

public class MutexDemo {

    @Test
public void test(){
final Mutex lock = new Mutex();
class Worker extends Thread {
@Override
public void run() {
// 一直不停在获取锁
while (true) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() +" hold lock, "+new Date());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() +" release lock, "+new Date());
}
}
} }
for (int i = 0; i < 10; i++) {
Worker worker = new Worker();
// 以守护进程运行,VM退出不影响运行,这里只是为了一个打印效果,去掉注释一直打印
worker.setDaemon(true);
worker.start();
}
// 每隔一秒换行
for (int j = 0; j < 10; j++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
}
}
}

(3)运行结果

Thread-0 hold lock, Tue Dec 08 16:26:42 CST 2020

Thread-0 release lock, Tue Dec 08 16:26:43 CST 2020
Thread-1 hold lock, Tue Dec 08 16:26:43 CST 2020 Thread-2 hold lock, Tue Dec 08 16:26:44 CST 2020
Thread-1 release lock, Tue Dec 08 16:26:44 CST 2020 Thread-3 hold lock, Tue Dec 08 16:26:45 CST 2020
Thread-2 release lock, Tue Dec 08 16:26:45 CST 2020 Thread-3 release lock, Tue Dec 08 16:26:46 CST 2020
Thread-4 hold lock, Tue Dec 08 16:26:46 CST 2020 Thread-4 release lock, Tue Dec 08 16:26:47 CST 2020
Thread-6 hold lock, Tue Dec 08 16:26:47 CST 2020 Thread-7 hold lock, Tue Dec 08 16:26:48 CST 2020
Thread-6 release lock, Tue Dec 08 16:26:48 CST 2020 Thread-7 release lock, Tue Dec 08 16:26:49 CST 2020
Thread-5 hold lock, Tue Dec 08 16:26:49 CST 2020 Thread-8 hold lock, Tue Dec 08 16:26:50 CST 2020
Thread-5 release lock, Tue Dec 08 16:26:50 CST 2020 Thread-8 release lock, Tue Dec 08 16:26:51 CST 2020
Thread-9 hold lock, Tue Dec 08 16:26:51 CST 2020

(4)结果分析

互斥锁的核心就是同一个同步状态只能被一个线程持有,其它线程等待持有线程释放才能竞争获取。截图一开始的运行结果分析:

Thread-0 hold lock, Tue Dec 08 16:26:42 CST 2020

Thread-0 release lock, Tue Dec 08 16:26:43 CST 2020
Thread-1 hold lock, Tue Dec 08 16:26:43 CST 2020 Thread-2 hold lock, Tue Dec 08 16:26:44 CST 2020
Thread-1 release lock, Tue Dec 08 16:26:44 CST 2020

10个线程不断竞争锁,一开始Thread-0在08 16:26:42获取到锁,持有锁1秒后在释放16:26:43时释放,同时Thread-1立马获取到锁,1秒后于16:26:44释放锁,同时Thread-2立马获取到了锁......

根据输出结果来说,完全符合Mutex作为互斥锁这个功能:同一时刻只有一个线程持有锁(同步状态),其它线程等待释放后才能获取

三、指定共享线程数目的共享锁

(1)代码实现(核心关键实现已经在代码中注释)

public class MyShareLock implements Lock {

    // 可以看到共享等待队列中的线程
public Collection<Thread> getSharedQueuedThreads(){
return syn.getSharedQueuedThreads();
}
private final Syn syn = new Syn(2); private static final class Syn extends AbstractQueuedSynchronizer{
int newShareCount=0;
Syn(int shareCount){
if (shareCount <= 0) {
throw new IllegalArgumentException("share count must large than zero");
}
// 设置初始共享同步状态
setState(shareCount);
} /**
* 共享锁指定数目
* @param reduceShareCount
* @return
*/
@Override
protected int tryAcquireShared(int reduceShareCount) { for (;;){
int currentShareCount = getState();
newShareCount = currentShareCount- reduceShareCount;
if (newShareCount < 0 ||
compareAndSetState(currentShareCount,newShareCount)) {
// newShareCount大于等于0才说明获取锁成功
if (newShareCount >= 0) {
// System.out.println(Thread.currentThread().getName()+" hold lock, current share count is "+newShareCount+", "+new Date());
}
// newShareCount小于0表示获取失败所以需要返回
// compareAndSetState(currentShareCount,newShareCount)为true自然表示成功需要返回
return newShareCount;
}
}
} @Override
protected boolean tryReleaseShared(int returnShareCount) {
for (;;){
int currentShareCount = getState();
newShareCount = currentShareCount + returnShareCount;
if (compareAndSetState(currentShareCount,newShareCount)) {
// System.out.println(Thread.currentThread().getName() +" release lock, current share count is "+newShareCount+", "+new Date());
return true;
}
}
}
protected int getShareCount(){
return getState();
}
} /**
* 调用内部同步器Syn的acquireShare方法
*/
@Override
public void lock() {
syn.acquireShared(1);
}
/**
* 调用内部同步器Syn的releaseShared方法
*/
@Override
public void unlock() {
syn.releaseShared(1);
} @Override
public void lockInterruptibly() throws InterruptedException {
if (Thread.interrupted()) {
throw new IllegalStateException();
}
syn.acquireInterruptibly(1);
} @Override
public boolean tryLock() {
return false;
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
} @Override
public Condition newCondition() {
return null;
}
}

(2)测试Demo

public class ShareLockTest {

    @Test
public void test(){
final MyShareLock lock = new MyShareLock();
class Worker extends Thread {
@Override
public void run() {
// 一直不停在获取锁
while (true) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() +" hold lock, "+new Date());
// System.out.println(lock.getSharedQueuedThreads());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() +" release lock, "+new Date());
}
}
} }
for (int i = 0; i < 10; i++) {
Worker worker = new Worker();
// 以守护进程运行,VM退出不影响运行,这里只是为了一个打印效果,去掉注释一直打印
worker.setDaemon(true);
worker.start();
}
// 每隔一秒换行
for (int j = 0; j < 10; j++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
}
}
}

(3)运行结果(结果可能不同)

Thread-1 hold lock, Tue Dec 08 16:36:05 CST 2020
Thread-0 hold lock, Tue Dec 08 16:36:05 CST 2020 Thread-0 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-4 hold lock, Tue Dec 08 16:36:06 CST 2020
Thread-1 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-2 hold lock, Tue Dec 08 16:36:06 CST 2020 Thread-4 release lock, Tue Dec 08 16:36:07 CST 2020
Thread-2 release lock, Tue Dec 08 16:36:07 CST 2020
Thread-5 hold lock, Tue Dec 08 16:36:07 CST 2020
Thread-8 hold lock, Tue Dec 08 16:36:07 CST 2020 Thread-8 release lock, Tue Dec 08 16:36:08 CST 2020
Thread-3 hold lock, Tue Dec 08 16:36:08 CST 2020
Thread-9 hold lock, Tue Dec 08 16:36:08 CST 2020
Thread-5 release lock, Tue Dec 08 16:36:08 CST 2020 Thread-6 hold lock, Tue Dec 08 16:36:09 CST 2020
Thread-7 hold lock, Tue Dec 08 16:36:09 CST 2020
Thread-3 release lock, Tue Dec 08 16:36:09 CST 2020
Thread-9 release lock, Tue Dec 08 16:36:09 CST 2020 Thread-6 release lock, Tue Dec 08 16:36:10 CST 2020
Thread-1 hold lock, Tue Dec 08 16:36:10 CST 2020
Thread-0 hold lock, Tue Dec 08 16:36:10 CST 2020
Thread-7 release lock, Tue Dec 08 16:36:10 CST 2020 Thread-1 release lock, Tue Dec 08 16:36:11 CST 2020
Thread-2 hold lock, Tue Dec 08 16:36:11 CST 2020
Thread-0 release lock, Tue Dec 08 16:36:11 CST 2020
Thread-4 hold lock, Tue Dec 08 16:36:11 CST 2020 Thread-2 release lock, Tue Dec 08 16:36:12 CST 2020
Thread-8 hold lock, Tue Dec 08 16:36:12 CST 2020
Thread-5 hold lock, Tue Dec 08 16:36:12 CST 2020
Thread-4 release lock, Tue Dec 08 16:36:12 CST 2020 Thread-5 release lock, Tue Dec 08 16:36:13 CST 2020
Thread-9 hold lock, Tue Dec 08 16:36:13 CST 2020
Thread-3 hold lock, Tue Dec 08 16:36:13 CST 2020
Thread-8 release lock, Tue Dec 08 16:36:13 CST 2020 Thread-3 release lock, Tue Dec 08 16:36:14 CST 2020
Thread-7 hold lock, Tue Dec 08 16:36:14 CST 2020
Thread-9 release lock, Tue Dec 08 16:36:14 CST 2020
Thread-6 hold lock, Tue Dec 08 16:36:14 CST 2020

(4)结果分析

该指定共享线程数量N的共享锁的最终目的就是多个线程可以持有锁(同步状态),达到共享线程数量N(代码中默认为2)时,其它线程将进入Queue等待获取同步结果,同一时刻只能最多有N个线程持有锁

同样地,我们分析开头运行结果:

Thread-1 hold lock, Tue Dec 08 16:36:05 CST 2020
Thread-0 hold lock, Tue Dec 08 16:36:05 CST 2020 Thread-0 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-4 hold lock, Tue Dec 08 16:36:06 CST 2020
Thread-1 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-2 hold lock, Tue Dec 08 16:36:06 CST 2020

10个线程不停竞争锁,一开始Thread-0与Thread-1在16:36:05时刻同时获取到了锁,此时已经达到共享数量的最大值,即N,之后持有锁1秒,Thread-0与Thread-1在16:36:06时刻立马释放锁,同时Thread-4与Thread-2立马退出等待队列立马竞争持有锁。

从结果来看,完全是符合ShareLock共享锁功能的:同一时刻最多允许N个线程持有锁,其它线程等待持有线程释放锁

学习JUC源码(2)——自定义同步组件的更多相关文章

  1. 学习JUC源码(1)——AQS同步队列(源码分析结合图文理解)

    前言 最近结合书籍<Java并发编程艺术>一直在看AQS的源码,发现AQS核心就是:利用内置的FIFO双向队列结构来实现线程排队获取int变量的同步状态,以此奠定了很多并发包中大部分实现基 ...

  2. 学习JUC源码(3)——Condition等待队列(源码分析结合图文理解)

    前言 在Java多线程中的wait/notify通信模式结尾就已经介绍过,Java线程之间有两种种等待/通知模式,在那篇博文中是利用Object监视器的方法(wait(),notify().notif ...

  3. 基于wtforms源码实现自定义form组件

    from flask import Flask,Markup,render_template,request,redirect from wtforms.form import Form from w ...

  4. 基于Django Form源码开发自定义Form组件

    import copy import re class ValidateError(Exception): def __init__(self, detail): self.detail = deta ...

  5. JUC源码学习笔记2——AQS共享和Semaphore,CountDownLatch

    本文主要讲述AQS的共享模式,共享和独占具有类似的套路,所以如果你不清楚AQS的独占的话,可以看我的<JUC源码学习笔记1> 主要参考内容有<Java并发编程的艺术>,< ...

  6. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  7. JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法

    JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...

  8. Android菜鸟的成长笔记(6)——剖析源码学自定义主题Theme

    原文:Android菜鸟的成长笔记(6)--剖析源码学自定义主题Theme 还记得在Android菜鸟的成长笔记(3)中我们曾经遇到了一个问题吗?"这个界面和真真的QQ界面还有点不同的就是上 ...

  9. JUC源码分析-集合篇(九)SynchronousQueue

    JUC源码分析-集合篇(九)SynchronousQueue SynchronousQueue 是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然.SynchronousQu ...

随机推荐

  1. Vue3教程:用 Vue3 开发小程序,这里有一份实际的代码案例!

    前言 寻寻觅觅冷冷清清,凄凄惨惨戚戚. Vue 3 发布以后,最近也在学习和写一些 Vue3 的 demo 和项目,我也一直想着什么时候能在小程序里使用新特性? 于是我翻遍了市面上的小程序框架,如 u ...

  2. vue+echarts+datav大屏数据展示及实现中国地图省市县下钻

    随着前端技术的飞速发展,大数据时代的来临,我们在开发项目时越来越多的客户会要求我们做一个数据展示的大屏,可以直观的展示用户想要的数据,同时炫酷的界面也会深受客户的喜欢. 大屏展示其实就是一堆的图表能够 ...

  3. mdp文件-Chapter4-MD.mdp

    终于到了mdp系列的第四篇,最终MD模拟的mdp文件 先上代码,md.mdp 1 title = OPLS Lysozyme MD simulation 2 ; Run parameters 3 in ...

  4. asp.net core 使用 TestServer 来做集成测试

    asp.net core 使用 TestServer 来做集成测试 Intro 之前我的项目里的集成测试是随机一个端口,每次都真实的启动一个 WebServer,之前也有看到过微软文档上 TestSe ...

  5. 06、MyBatis 逆向工程

    1.MyBatis逆向简介   mybatis需要程序员自己编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java.mapper.x ...

  6. jenkins master/slave模式

    master是主机,只有master装jenkins slave是小弟机无需装jenkins,主要执行master分配的任务 一.新建slave 1.新建slave的方法:点击magian jenki ...

  7. 实时检测微信域名防红拦截检测API系统,最新腾讯域名屏蔽检测官方接口

    最近手里有个项目需要检测域名在微信里是否可以打开,如果被微信拦截,则需要进行下一步操作,所以需要判断域名的状态,但是微信官方并没有提供相关查询的方法,最后在网上找到了这个接口地址,分享给有需要的朋友. ...

  8. Idea中如何导入jar包

    1.首先在idea左上角找到" File ",然后找到 "Project structure" 2.接着选择 " java ",选择后接着会 ...

  9. Spring Boot第一天

    1.首先在idea中创建一个maven项目,创建成功后在pom.xml中添加SpringBoot相关的依赖 <!--引入SpringBoot相关的依赖--> <parent> ...

  10. LeetCode双周赛#34

    5492. 分割字符串的方案数 #组合公式 #乘法原理 #区间分割 题目链接 题意 给定01二进制串\(s\),可将\(s\)分割为三个非空 字符串\(s_1,s_2,s_3\),即(\(s_1+s_ ...