当前位置: 首页 > news >正文

我使出这“三板斧”(分段锁、哈希锁、弱引用锁)灭霸跑了......

有同学说,学了Java那么多锁,还是没能锁住灭霸,本文教你“三板斧”,锁灭霸足矣。

据说,没几个人能真正参透这“三板斧”的精髓,你是不是那个有缘人呢?

最近,在工作上碰见了一些高并发的场景需要加锁来保证业务逻辑的正确性,并且要求加锁后性能不能受到太大的影响。初步的想法是通过数据的时间戳、id等关键字来加锁,从而保证不同类型数据处理的并发性。而Java自身api提供的锁粒度太大,很难同时满足这些需求,于是自己动手写了几个简单的扩展。。。

分段锁

借鉴ConcurrentHashMap的分段思想,先生成一定数量的锁,具体使用的时候再根据key来返回对应的lock。这是几个实现里最简单,性能最高,也是最终被采用的锁策略,代码如下:

  • 分段锁,系统提供一定数量的原始锁,根据传入对象的哈希值获取对应的锁并加锁
  • 注意:要锁的对象的哈希值如果发生改变,有可能导致锁无法成功释放!!!
public class SegmentLock<T> {
  private Integer segments = 16;//默认分段数量
  private final HashMap<Integer, ReentrantLock> lockMap = new HashMap<>();
 
  public SegmentLock() {
    init(null, false);
  }
 
  public SegmentLock(Integer counts, boolean fair) {
    init(counts, fair);
  }
 
  private void init(Integer counts, boolean fair) {
    if (counts != null) {
      segments = counts;
    }
    for (int i = 0; i < segments; i++) {
      lockMap.put(i, new ReentrantLock(fair));
    }
  }
 
  public void lock(T key) {
    ReentrantLock lock = lockMap.get((key.hashCode()>>>1) % segments);
    lock.lock();
  }
 
  public void unlock(T key) {
    ReentrantLock lock = lockMap.get((key.hashCode()>>>1) % segments);
    lock.unlock();
  }
}
复制代码

哈希锁

上述分段锁的基础上发展起来的第二种锁策略,目的是实现真正意义上的细粒度锁。每个哈希值不同的对象都能获得自己独立的锁。在测试中,在被锁住的代码执行速度飞快的情况下,效率比分段锁慢 30% 左右。如果有长耗时操作,感觉表现应该会更好。代码如下:

public class HashLock<T> {
  private boolean isFair = false;
  private final SegmentLock<T> segmentLock = new SegmentLock<>();//分段锁
  private final ConcurrentHashMap<T, LockInfo> lockMap = new ConcurrentHashMap<>();
 
  public HashLock() {
  }
 
  public HashLock(boolean fair) {
    isFair = fair;
  }
 
  public void lock(T key) {
    LockInfo lockInfo;
    segmentLock.lock(key);
    try {
      lockInfo = lockMap.get(key);
      if (lockInfo == null) {
        lockInfo = new LockInfo(isFair);
        lockMap.put(key, lockInfo);
      } else {
        lockInfo.count.incrementAndGet();
      }
    } finally {
      segmentLock.unlock(key);
    }
    lockInfo.lock.lock();
  }
 
  public void unlock(T key) {
    LockInfo lockInfo = lockMap.get(key);
    if (lockInfo.count.get() == 1) {
      segmentLock.lock(key);
      try {
        if (lockInfo.count.get() == 1) {
          lockMap.remove(key);
        }
      } finally {
        segmentLock.unlock(key);
      }
    }
    lockInfo.count.decrementAndGet();
    lockInfo.unlock();
  }
 
  private static class LockInfo {
    public ReentrantLock lock;
    public AtomicInteger count = new AtomicInteger(1);
 
    private LockInfo(boolean fair) {
      this.lock = new ReentrantLock(fair);
    }
 
    public void lock() {
      this.lock.lock();
    }
 
    public void unlock() {
      this.lock.unlock();
    }
  }
}
复制代码

弱引用锁

哈希锁因为引入的分段锁来保证锁创建和销毁的同步,总感觉有点瑕疵,所以写了第三个锁来寻求更好的性能和更细粒度的锁。这个锁的思想是借助java的弱引用来创建锁,把锁的销毁交给jvm的垃圾回收,来避免额外的消耗。

有点遗憾的是因为使用了ConcurrentHashMap作为锁的容器,所以没能真正意义上的摆脱分段锁。这个锁的性能比 HashLock 快10% 左右。锁代码:

  • 弱引用锁,为每个独立的哈希值提供独立的锁功能
public class WeakHashLock<T> {
  private ConcurrentHashMap<T, WeakLockRef<T, ReentrantLock>> lockMap = new ConcurrentHashMap<>();
  private ReferenceQueue<ReentrantLock> queue = new ReferenceQueue<>();
 
  public ReentrantLock get(T key) {
    if (lockMap.size() > 1000) {
      clearEmptyRef();
    }
    WeakReference<ReentrantLock> lockRef = lockMap.get(key);
    ReentrantLock lock = (lockRef == null ? null : lockRef.get());
    while (lock == null) {
      lockMap.putIfAbsent(key, new WeakLockRef<>(new ReentrantLock(), queue, key));
      lockRef = lockMap.get(key);
      lock = (lockRef == null ? null : lockRef.get());
      if (lock != null) {
        return lock;
      }
      clearEmptyRef();
    }
    return lock;
  }
 
  @SuppressWarnings("unchecked")
  private void clearEmptyRef() {
    Reference<? extends ReentrantLock> ref;
    while ((ref = queue.poll()) != null) {
      WeakLockRef<T, ? extends ReentrantLock> weakLockRef = (WeakLockRef<T, ? extends ReentrantLock>) ref;
      lockMap.remove(weakLockRef.key);
    }
  }
 
  private static final class WeakLockRef<T, K> extends WeakReference<K> {
    final T key;
 
    private WeakLockRef(K referent, ReferenceQueue<? super K> q, T key) {
      super(referent, q);
      this.key = key;
    }
  }
}
复制代码

后记

最开始想借助 LockSupport 和 AQS 来实现细粒度锁,写着写着发现正在实现的东西和Java 原生的锁区别不大,于是放弃改为对java自带锁的封装,浪费了不少时间。

实际上在实现了这些细粒度锁之后,又有了新的想法,比如可以通过分段思想将数据提交给专门的线程来处理,可以减少大量线程的阻塞时间,留给有缘人去探索。。。

END

读者福利,欢迎大家进我的技术交流群猥琐发育,一起抱团取暖吗?



转载于:https://juejin.im/post/5d00c4e0e51d45775c73dcc4

相关文章:

  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • zimbra 证书过期--zimbra使用
  • 编程之美 象棋将帅问题
  • 你会写单元测试吗
  • 一道题浅谈【作业调度】与【进程调度】
  • imagick-3.1.0RC2 安装错误
  • Taro 1.3 震撼发布:全面支持 JSX 语法和 HOOKS
  • Android Adapter
  • ognl表达式
  • 直播APP关于后期运营你知道多少?
  • 【新手向】vim快捷注释与删除操作
  • Maven搭建SpringMVC+Mybatis项目详解
  • Access restriction: The method createJPEGEncoder(OutputStream) from the type JPEGCodec is not access
  • 路由器简单的基础实验
  • Android(java)学习笔记18:单例模式
  • 07.Android之多媒体问题
  • ES6简单总结(搭配简单的讲解和小案例)
  • linux学习笔记
  • mysql中InnoDB引擎中页的概念
  • React as a UI Runtime(五、列表)
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 蓝海存储开关机注意事项总结
  • 力扣(LeetCode)357
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 排序算法之--选择排序
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 我从编程教室毕业
  • 线上 python http server profile 实践
  • 一个完整Java Web项目背后的密码
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • puppet连载22:define用法
  • #etcd#安装时出错
  • #pragma data_seg 共享数据区(转)
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (python)数据结构---字典
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (十八)三元表达式和列表解析
  • (转)大型网站架构演变和知识体系
  • .aanva
  • .gitattributes 文件
  • .libPaths()设置包加载目录
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .NET Core 和 .NET Framework 中的 MEF2
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .net6Api后台+uniapp导出Excel
  • .NetCore 如何动态路由
  • .NET国产化改造探索(三)、银河麒麟安装.NET 8环境
  • .project文件
  • @Autowired多个相同类型bean装配问题
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(朱雀组)
  • [BZOJ 1040] 骑士
  • [CC2642R1][VSCODE+Embedded IDE+IAR Build+Cortex-Debug] TI CC2642R1基于VsCode的开发环境
  • [CTO札记]盛大文学公司名称对联