java juc 包下面已经提供了很多并发锁工具供我们使用,但在日常开发中,为了各种原因我们总是会用多线程来并发处理一些问题,然而并不是所有的场景都可以使用juc 或者java本身提供的锁来方便的帮助我们控制多线程带来的并发问题,这个时候就需要我们根据自己的业务场景来子实现定制一把我们自己专属的锁,来满足我们的需要。

  假设系统对接了很多第三方公司,来帮助我们完成业务,但这些第三方的服务接口稳定性参差不齐,以往的过程中我们可能会做一些监控措施来帮助我们监控接口的稳定性,但这会存在一个问题,就是当我们监控到操作失败的时候其实已经会有用户产生操作失败的结果了,这对重视用户体验的互联网公司肯定是不能忍的,为此我们可以每个用户来访问时都同时调用多个第三方,只要有一个返回结果,我们就可以给用户做相应的展示,这样即使有一两个第三方出现故障对用户也是无感知的,但另一个问题来了,同时并发调用第三方我怎么选哪个结果呢,很简单,当然是返回最快的了!具体如何选用最快的返回结果就用到我们今天的主题了,定制自己的锁。

  上面所说的大致流程可以描述为这样:接受用户请求  →  多线程组装报文调用第三方 → 阻塞等待 → 任意结果返回唤醒主线程继续处理。基于此流程很自然想到这个阻塞其实就可以用多线程中的锁来实现,主线程在将任务提交给线程池多线程处理后,去获取一个锁,而这个锁需要在线程中第一个第三方返回结果时才能获取到,这样就让主线程继续执行,有了大概思路,我们来看下具体如何实现。

  看过java 源码的同学对AbstractQueuedSynchronizer一定不会陌生,java中的很多锁ReentrantLock 、ReadWriteLock  、ReentrantReadWriteLock 和一些其他的并发工具CountDownLatch、 Semaphore等都基于此抽象类实现,AbstractQueuedSynchronizer中通过一个FIFO队列来管理等待加锁的线程,通过一个state的int变量控制线程加锁状态,其内部也帮我实现了线程获得锁和挂起的方法,我们这里参考CountDownLatch来实现,因为我们的需求和CountDownLatch正好相反,CountDownLatch是多个线程都处理完才能继续,而我们是只要有一个处理完就能继续,简单来说就是主线程唤醒的判断条件不一致。先来看下CountDownLatch的使用:

  

public static void main(String[] args) throws InterruptedException {
CountDownLatch await = new CountDownLatch(5); // 依次创建并启动线程
for (int i = 0; i < 5; ++i) {
new Thread(new MyRunnable(await)).start();
}
await.await();
System.out.println("over!");
} class MyRunnable implements Runnable { private final CountDownLatch await; public MyRunnable(CountDownLatch await) {
this.await = await;
} public void run() {
try {
//业务处理
await.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

  熟悉套路之后为了更好的说明,我们引入其部分源码(jdk 1.8中部分方法)

 private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
setState(count);
} int getCount() {
return getState();
} protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
} protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
} private final Sync sync; public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
} public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
} public void countDown() {
sync.releaseShared(1);
} }

  代码中可以看到,CountDownLatch主要就是倚靠一个内部类Sync来实现,而Sync实现了AbstractQueuedSynchronizer的tryAcquireShared和tryReleaseShared方法,这两方法的主要目的是:tryAcquireShared就是在调用await方法后来判断是否需要阻塞还是执行,tryReleaseShared 就是用来释放state的状态,而state的状态又影响了tryAcquireShared的返回结果,决定了线程是阻塞还是会被唤起继续执行,具体的判断逻辑是在AbstractQueuedSynchronizer中的acquireSharedInterruptibly方法中

  

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

  基于此我们的大概思路就是,利用自己的类实现tryAcquireShared和tryReleaseShared方法来帮助我们管理state状态,决定主线程什么时候可以获得锁继续运行,什么时候需要阻塞。依靠AbstractQueuedSynchronizer的内部机制帮助我们及时获取子线程的处理信息,最快的回到主线程来处理我们业务逻辑。实现代码如下:

package com.chengxiansheng.common;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer; public class ReqBraker { private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
    
    protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
} protected boolean tryReleaseShared(int releases) {
int c = getState();
c = 0;
return true;
}
} private final Sync sync; private ResultDto resultDto;//处理结果 public ReqBraker(){
this.sync = new Sync(1);
} /**
* 请求返回
* 此处要考虑接口调用失败的情况,如果失败要等待其他线程则不调用此方法
*/
public void reqReuturn(ResultDto resultDto){
this.resultDto = resultDto;
sync.tryReleaseShared(1);
} /**
* 主线程等待指定最长等待时间
* @param timeout
* @param unit
* @return
* @throws InterruptedException
*/
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
} /**
* 获取处理结果
* @return
*/
public ResultDto getResultDto(){
return resultDto;
}
  
}
package com.chengxiansheng.common;

public class RequsetWorker implements Runnable  {

    private RequestDto requestDto;//请求信息

    private ReqBraker  reqBraker;

    public RequsetWorker(RequestDto requestDto, ReqBraker  reqBraker){
this.requestDto = requestDto;
this.reqBraker = reqBraker;
} public void run() {
//请求第三方
//判断返回结果
if(requestDto.isSeccess()){
reqBraker.reqReuturn(resultDto);
}
}
}

  具体的使用方法如下:

public void requestService(RequestDto request, List<Service> serviceList) {
try {
ReqBraker braker = new ReqBraker();
for (Service service : serviceList) {
executorService.submit(new RequsetWorker(braker, request);
}
braker.await(5, TimeUnit.SECONDS); //请求处理完成 最长时间5S ResultDto result = braker.getResultDto();
if(result == null){ //可能超过了最终等待时间
//返回处理失败;
}
//返回处理结果;
  } catch (Exception e) {
//异常处理
}

  当然实际的使用中可能会比这复杂,因为我们要有各种业务处理情况去要考虑,本文只是一个范例来帮助大家介绍一个新的思路来解决问题,合理利用Java中的工具的同事我们也要理解其实现原理,来定制符合我们使用场景的方法,才能写出更高效的代码,实现更高效的系统。AbstractQueuedSynchronizer的作用也远不止此,但我们掌握了它就可以更好的玩转多线程,玩转并发,来创新的实现各种复杂处理和逻辑。

自己实现定制自己的专属java锁,来高效规避不稳定的第三方的更多相关文章

  1. java 锁!

    问题:如何实现死锁. 关键: 1 两个线程ta.tb 2 两个对象a.b 3 ta拥有a的锁,同时在这个锁定的过程中,需要b的锁:tb拥有b的锁,同时在这个锁定的过程中,需要a的锁: 关键的实现难点是 ...

  2. Java锁(一)之内存模型

    想要了解Java锁机制.引发的线程安全问题以及数据一致性问题,有必要了解内存模型,机理机制了解清楚了,这些问题也就应声而解了. 一.主内存和工作内存 Java内存模型分为主内存和工作内存,所有的变量都 ...

  3. Java锁的种类

    转载自:---->http://ifeve.com/java_lock_see/ Java锁的种类以及辨析锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchroniz ...

  4. JAVA 锁

    JAVA 锁 锁的概念 Java中的锁是控制资源访问的一种方式.它弥补了synchronized的可操作性不强的不足. Java的锁都实现了Lock接口.Lock结构定义了锁的基本操作. 函数 解释 ...

  5. JAVA锁的可重入性

    机制:每个锁都关联一个请求计数器和一个占有他的线程,当请求计数器为0时,这个锁可以被认为是unhled的,当一个线程请求一个unheld的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线 ...

  6. 定制化Azure站点Java运行环境(5)

    Java 8下PermGen及参数设置 在上一章节中,我们定制化使用了Java 8环境,使用我们的测试页面打印出了JVM基本参数,但如果我们自己观察,会发现在MXBeans中,没有出现PermGen的 ...

  7. 定制化Azure站点Java运行环境(3)

    定制化Azure Website提供的默认的Tomcat和JDK环境 在我们之前的测试中,如果你访问你的WEB站点URL时不加任何上下文,实际上你看到的web界面是系统自带的测试页面index.jsp ...

  8. JAVA 锁之 Synchronied

    ■ Java 锁 1. 锁的内存语义 锁可以让临界区互斥执行,还可以让释放锁的线程向同一个锁的线程发送消息 锁的释放要遵循 Happens-before 原则(锁规则:解锁必然发生在随后的加锁之前) ...

  9. java锁与监视器概念 为什么wait、notify、notifyAll定义在Object中 多线程中篇(九)

    在Java中,与线程通信相关的几个方法,是定义在Object中的,大家都知道Object是Java中所有类的超类 在Java中,所有的类都是Object,借助于一个统一的形式Object,显然在有些处 ...

随机推荐

  1. c#代码安装字体文件

    public class FontOperate { [DllImport("kernel32.dll", SetLastError = true)] static extern ...

  2. select ,update 加锁

    最近我在弄一个项目,其中涉及到了数据批量导入数据库的过程,在导入数据的时候,每一条数据会生成一个唯一标识,但是我发现有些数据的标识重复了.我在网上查了一下说这是“数据库 并发性”的问题解决方案,上锁. ...

  3. ASP.NET WebForm项目--页面正确返回404及500等状态

    实现方法 项目路径 <system.webServer> <httpErrors errorMode="Custom" > <remove statu ...

  4. C语言宏定义##连接符和#符的使用(MFC就是靠##自动把消息和消息函数对应起来了,借助宏来减少switch case代码的编写量)

    C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念).下面对常遇到的宏的使用问题做了简单总结. 关于#和## 在C语言的宏中,#的功能是将其后面 ...

  5. C# Task 的用法

    C# Task 的用法(转自:http://www.wxzzz.com/683.html#) 其实Task跟线程池ThreadPool的功能类似,不过写起来更为简单,直观.代码更简洁了,使用Task来 ...

  6. oracle10g登录em后,提示“java.lang.Exception: Exception in sending Request :: null”

    出现错误时登录企业管理器时出现的界面 出现这种错误一般是因为没有设置时区,一般默认的是agentTZRegion=GMT,也就是GMT.所以大家只要设置了这个东西,然后重新启动dbconsole就可以 ...

  7. Spring之bean后处理器

    Bean后处理器是一种特殊的Bean,容器中所有的Bean在初始化时,均会自动执行该类的两个方法.由于该Bean是由其它Bean自动调用执行,不是程序员手工调用,故此Bean无须id属性.需要做的是, ...

  8. netcore mvc快速开发系统(菜单,角色,权限[精确到按钮])开源

    AntMgr https://github.com/yuzd/AntMgr 基于netcore2.0 mvc 开发的 快速搭建具有如下特色的后台管理系统 特色: 用户管理 菜单管理 角色管理 权限管理 ...

  9. 11 CSS的三种引入方式和基本选择器

    <!-- 整体说明: 1.CSS的三种引入方式 (1)行内样式 (2)内接样式 (3)外接样式 2.CSS的基本选择器 (1)id选择器 (引用方式:#id) (2)标签选择器(引用方式:标签名 ...

  10. 视频编解码的理论和实践2:Ffmpeg视频编解码

    近几年,视频编解码技术在理论及应用方面都取得了重大的进展,越来越多的人想要了解编解码技术.因此,网易云信研发工程师为大家进行了归纳梳理,从理论及实践两个方面简单介绍视频编解码技术. 相关阅读推荐 &l ...