序言

上一节我们学习了异步查询转同步的 7 种实现方式,今天我们就来学习一下,如何对其进行封装,使其成为一个更加便于使用的工具。

思维导图如下:

拓展阅读

java 手写并发框架(1)异步查询转同步的 7 种实现方式

异步转同步的便利性

实现方式

  • 循环等待

  • wait & notify

  • 使用条件锁

  • 使用 CountDownLatch

  • 使用 CyclicBarrier

  • Future

  • Spring EventListener

上一节我们已经对上面的 7 种实现方式进行了详细的介绍,没有看过的同学可以去简单回顾一下。

但是这样个人觉得还是不够方便,懒惰是进步的阶梯。

更进一步简化

我们希望达到下面的效果:

@Sync
public String queryId() {
System.out.println("开始查询");
return id;
} @SyncCallback(value = "queryId")
public void queryIdCallback() {
System.out.println("回调函数执行");
id = "123";
}

通过注解直接需要同步的方法,和回调的方法,代码中直接调用即可。

我们首先实现基于字节码增强的版本,后续将实现整合 spring, springboot 的版本。

锁的代码实现

锁的定义

我们将原来的实现抽象为加锁和解锁,为了便于拓展,接口定义如下:

package com.github.houbb.sync.api.api;

/**
* @author binbin.hou
* @since 0.0.1
*/
public interface ISyncLock { /**
* 等待策略
* @param context 上下文
* @since 0.0.1
*/
void lock(final ISyncLockContext context); /**
* 解锁策略
* @param context 上下文
* @since 0.0.1
*/
void unlock(final ISyncUnlockContext context); }

其中上下文加锁和解锁做了区分,不过暂时内容是一样的。

主要是超时时间和单位:

package com.github.houbb.sync.api.api;

import java.util.concurrent.TimeUnit;

/**
* @author binbin.hou
* @since 0.0.1
*/
public interface ISyncLockContext { /**
* 超时时间
* @return 结果
*/
long timeout(); /**
* 超时时间单位
* @return 结果
*/
TimeUnit timeUnit(); }

锁策略实现

我们本节主要实现下上一节中的几种锁实现。

目前我们选择其中的是个进行实现:

wait & notify

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext;
import com.github.houbb.sync.api.exception.SyncRuntimeException; /**
* 等待通知同步
*
* @author binbin.hou
* @since 0.0.1
*/
public class WaitNotifyLock implements ISyncLock { private static final Log log = LogFactory.getLog(WaitNotifyLock.class); /**
* 声明对象
*/
private final Object lock = new Object(); @Override
public synchronized void lock(ISyncLockContext context) {
synchronized (lock) {
try {
long timeoutMills = context.timeUnit().toMillis(context.timeout());
log.info("进入等待,超时时间为:{}ms", timeoutMills);
lock.wait(timeoutMills);
} catch (InterruptedException e) {
log.error("中断异常", e);
throw new SyncRuntimeException(e);
}
}
} @Override
public void unlock(ISyncUnlockContext context) {
synchronized (lock) {
log.info("唤醒所有等待线程");
lock.notifyAll();
}
} }

加锁的部分比较简单,我们从上下文中获取超时时间和超时单位,直接和上一节内容类似,调用即可。

至于上下文中的信息是怎么来的,我们后续就会讲解。

条件锁实现

这个在有了上一节的基础之后也非常简单。

核心流程:

(1)创建锁

(2)获取锁的 condition

(3)执行加锁和解锁

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* 等待通知同步
*
* @author binbin.hou
* @since 0.0.1
*/
public class LockConditionLock implements ISyncLock { private static final Log log = LogFactory.getLog(LockConditionLock.class); private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); @Override
public synchronized void lock(ISyncLockContext context) {
lock.lock();
try{
log.info("程序进入锁定状态");
condition.await(context.timeout(), context.timeUnit());
} catch (InterruptedException e) {
log.error("程序锁定状态异常", e);
} finally {
lock.unlock();
}
} @Override
public void unlock(ISyncUnlockContext context) {
lock.lock();
try{
log.info("解锁状态,唤醒所有等待线程。");
condition.signalAll();
} finally {
lock.unlock();
}
} }

CountDownLatch 实现

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext; import java.util.concurrent.CountDownLatch; /**
* 等待通知同步
*
* @author binbin.hou
* @since 0.0.1
*/
public class CountDownLatchLock implements ISyncLock { private static final Log log = LogFactory.getLog(CountDownLatchLock.class); /**
* 闭锁
* 调用1次,后续方法即可通行。
*/
private CountDownLatch countDownLatch = new CountDownLatch(1); @Override
public synchronized void lock(ISyncLockContext context) {
countDownLatch = new CountDownLatch(1); try {
log.info("进入等待,超时时间为:{},超时单位:{}", context.timeout(),
context.timeUnit());
boolean result = countDownLatch.await(context.timeout(), context.timeUnit());
log.info("等待结果: {}", result);
} catch (InterruptedException e) {
log.error("锁中断异常", e);
}
} @Override
public void unlock(ISyncUnlockContext context) {
log.info("执行 unlock 操作");
countDownLatch.countDown();
} }

注意:这里为了保证 countDownLatch 可以多次使用,我们在每一次加锁的时候,都会重新创建 CountDownLatch。

CyclicBarrierLock 锁实现

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext;
import com.github.houbb.sync.api.exception.SyncRuntimeException; import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeoutException; /**
* @author binbin.hou
* @since 0.0.1
*/
public class CyclicBarrierLock implements ISyncLock { private static final Log log = LogFactory.getLog(CyclicBarrierLock.class); private final CyclicBarrier cyclicBarrier = new CyclicBarrier(2); @Override
public synchronized void lock(ISyncLockContext context) {
try {
log.info("进入锁定状态, timeout:{}, timeunit: {}",
context.timeout(), context.timeUnit());
cyclicBarrier.await(context.timeout(), context.timeUnit()); log.info("重置 cyclicBarrier");
cyclicBarrier.reset();
} catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
log.error("锁定时遇到异常", e);
throw new SyncRuntimeException(e);
}
} @Override
public void unlock(ISyncUnlockContext context) {
try {
log.info("解锁信息");
cyclicBarrier.await(context.timeout(), context.timeUnit());
} catch (InterruptedException | TimeoutException | BrokenBarrierException e) {
log.error("解锁时遇到异常", e);
}
} }

这里和 CountDownLatchLock 的实现非常类似,不过 CyclicBarrier 有一个好处,就是可以复用。

我们在每一次解锁之后,重置一下栅栏:

log.info("重置 cyclicBarrier");
cyclicBarrier.reset();

锁的工具类

为了简单的生成上述几种锁的实例,我们提供了一个简单的工具类方法:

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.heaven.support.instance.impl.Instances;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.constant.LockType; import java.util.HashMap;
import java.util.Map; /**
* 锁策略
* @author binbin.hou
* @since 0.0.1
*/
public final class Locks { private Locks(){} /**
* MAP 信息
* @since 0.0.1
*/
private static final Map<LockType, ISyncLock> MAP = new HashMap<>(); static {
MAP.put(LockType.WAIT_NOTIFY, waitNotify());
MAP.put(LockType.COUNT_DOWN_LATCH, countDownLatch());
MAP.put(LockType.CYCLIC_BARRIER, cyclicBarrier());
MAP.put(LockType.LOCK_CONDITION, lockCondition());
} /**
* 获取锁实现
* @param lockType 锁类型
* @return 实现
* @since 0.0.1
*/
public static ISyncLock getLock(final LockType lockType) {
return MAP.get(lockType);
} /**
* @since 0.0.1
* @return 实现
*/
private static ISyncLock waitNotify() {
return Instances.singleton(WaitNotifyLock.class);
} /**
* @since 0.0.1
* @return 实现
*/
private static ISyncLock countDownLatch() {
return Instances.singleton(CountDownLatchLock.class);
} /**
* @since 0.0.1
* @return 实现
*/
private static ISyncLock lockCondition() {
return Instances.singleton(LockConditionLock.class);
} /**
* @since 0.0.1
* @return 实现
*/
private static ISyncLock cyclicBarrier() {
return Instances.singleton(CyclicBarrierLock.class);
} }

上述的锁实现都是线程安全的,所以全部使用单例模式创建。

LockType 类是一个锁的枚举类,会在注解中使用。

小结

好了,到这里我们就把上一节中的常见的 4 种锁策略就封装完成了。

你可能好奇上下文的时间信息哪里来?这些锁又是如何被调用的?

我们将通过注解+字节码增强的方式来实现调用(就是 aop 的原理),由于篇幅原因,字节码篇幅较长,为了阅读体验,实现部分将放在下一节。

感兴趣的可以关注一下,便于实时接收最新内容。

觉得本文对你有帮助的话,欢迎点赞评论收藏转发一波。你的鼓励,是我最大的动力~

不知道你有哪些收获呢?或者有其他更多的想法,欢迎留言区和我一起讨论,期待与你的思考相遇。

文中如果链接失效,可以点击 {阅读原文}。

java 手写并发框架(二)异步转同步框架封装锁策略的更多相关文章

  1. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  2. 教你如何使用Java手写一个基于链表的队列

    在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...

  3. opencv 手写选择题阅卷 (二)字符识别

    opencv 手写选择题阅卷 (二)字符识别 选择题基本上只需要识别ABCD和空五个内容,理论上应该识别率比较高的,识别代码参考了网上搜索的代码,因为参考的网址比较多,现在也弄不清是参考何处的代码了, ...

  4. tensorflow笔记(五)之MNIST手写识别系列二

    tensorflow笔记(五)之MNIST手写识别系列二 版权声明:本文为博主原创文章,转载请指明转载地址 http://www.cnblogs.com/fydeblog/p/7455233.html ...

  5. java 手写 jvm高性能缓存

    java 手写 jvm高性能缓存,键值对存储,队列存储,存储超时设置 缓存接口 package com.ws.commons.cache; import java.util.function.Func ...

  6. java手写多级缓存

    多级缓存实现类,时间有限,该类未抽取接口,目前只支持两级缓存:JVM缓存(实现 请查看上一篇:java 手写JVM高性能缓存).redis缓存(在spring 的 redisTemplate 基础实现 ...

  7. 利用神经网络算法的C#手写数字识别(二)

    利用神经网络算法的C#手写数字识别(二)   本篇主要内容: 让项目编译通过,并能打开图片进行识别.   1. 从上一篇<利用神经网络算法的C#手写数字识别>中的源码地址下载源码与资源, ...

  8. 手写AVL平衡二叉搜索树

    手写AVL平衡二叉搜索树 二叉搜索树的局限性 先说一下什么是二叉搜索树,二叉树每个节点只有两个节点,二叉搜索树的每个左子节点的值小于其父节点的值,每个右子节点的值大于其左子节点的值.如下图: 二叉搜索 ...

  9. 简单的node爬虫练手,循环中的异步转同步

    简单的node爬虫练手,循环中的异步转同步 转载:https://blog.csdn.net/qq_24504525/article/details/77856989 看到网上一些基于node做的爬虫 ...

  10. JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

    JAVA之旅(十四)--静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制 JAVA之旅,一路有你,加油! 一.静态同步函数的锁是clas ...

随机推荐

  1. Nginx日志规则以及根据日志进行性能问题判断的思路

    Nginx日志规则以及根据日志进行性能问题判断的思路 背景 Nginx是开源方案里面能实现反向代理 负载均衡的首选. 但是有时候性能出问题比较难以分析和定位, 不知道是不是nginx的瓶颈 性能问题的 ...

  2. [转帖]HikariCP常用监控指标与故障排查实战

    编者有言:本书由资深数据库连接池专家撰写,褚霸.德哥.张亮.吴晟等近10位专家高度评价,从设计思想.功能使用.原理实现.工程实践.技术扩展5个维度对HikariCP进行全面讲解和深入分析. 本文将带你 ...

  3. [转帖]br备份时排除某个库

    https://tidb.net/blog/2a88149e?utm_source=tidb-community&utm_medium=referral&utm_campaign=re ...

  4. [转帖]tidb-lightning 逻辑模式导入

    https://docs.pingcap.com/zh/tidb/stable/tidb-lightning-configuration 本文档介绍如何编写逻辑导入模式的配置文件,如何进行性能调优等内 ...

  5. [转帖]iptables命令详解和举例(完整版)

    1.防火墙概述 防火墙,其实说白了讲,就是用于实现Linux下访问控制的功能的,它分为硬件的或者软件的防火墙两种.无论是在哪个网络中,防火墙工作的地方一定是在网络的边缘.而我们的任务就是需要去定义到底 ...

  6. CentOS7 通过移植二进制文件的方式安装redis、nginx以及dotnet core的简单办法

    新的centos机器安装预制软件比较麻烦 最简单的方法是在保证服务器或者是虚拟机硬件架构相同,并且操作系统版本差别不是很大的情况下, 直接使用其他机器已经变异好的二进制文件最为简单. 比如本次 我这边 ...

  7. Grafana监控java应用以及vCenter的方法

    Grafana监控java应用以及vCenter的方法 背景 最开始弄过vCenter的监控. 但是发现很多地方已经不合适了. 今天看了下jmx监控 java的应用. 顺便监控了下vCenter. 这 ...

  8. C# AsyncLocal 是如何实现 Thread 间传值

    一:背景 1. 讲故事 这个问题的由来是在.NET高级调试训练营第十期分享ThreadStatic底层玩法的时候,有朋友提出了AsyncLocal是如何实现的,虽然做了口头上的表述,但总还是会不具体, ...

  9. el-popover 点击取消按钮,弹窗仍然无法关闭

    <el-popover placement="bottom" width="200" :ref="aa" :visible.sync= ...

  10. 把Unity的日志保存到文件中

    Unity的日志事件 Unity提供了两个日志回调API,这两个回调函数的参数都是一样的,通过这个API可以在真机上把Debug.Log/LogWarning/LogError 日志输出到文件中保存, ...