几种JAVA细粒度锁的实现方式

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

1. 分段锁

借鉴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();
  }
}

2. 哈希锁

上述分段锁的基础上发展起来的第二种锁策略,目的是实现真正意义上的细粒度锁。每个哈希值不同的对象都能获得自己独立的锁。在测试中,在被锁住的代码执行速度飞快的情况下,效率比分段锁慢 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();
    }
  }
}

3. 弱引用锁

哈希锁因为引入的分段锁来保证锁创建和销毁的同步,总感觉有点瑕疵,所以写了第三个锁来寻求更好的性能和更细粒度的锁。这个锁的思想是借助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自带锁的封装,浪费了不少时间。

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

(0)

相关推荐

  • java基于ConcurrentHashMap设计细粒度实现代码

    细粒度锁: java中的几种锁:synchronized,ReentrantLock,ReentrantReadWriteLock已基本可以满足编程需求,但其粒度都太大,同一时刻只有一个线程能进入同步块,这对于某些高并发的场景并不适用.比如银行客户a向b转账,c向d转账,假如这两个线程并发,代码其实不需要同步.但是同时有线程3,e向b转账,那么对b而言必须加入同步.这时需要考虑锁的粒度问题,即细粒度锁. 网上搜寻了一些关于java细粒度锁的介绍文章,大部分是提供思路,比如乐观锁,String.i

  • 几种JAVA细粒度锁的实现方式

    最近在工作上碰见了一些高并发的场景需要加锁来保证业务逻辑的正确性,并且要求加锁后性能不能受到太大的影响.初步的想法是通过数据的时间戳,id等关键字来加锁,从而保证不同类型数据处理的并发性.而java自身api提供的锁粒度太大,很难同时满足这些需求,于是自己动手写了几个简单的扩展... 1. 分段锁 借鉴concurrentHashMap的分段思想,先生成一定数量的锁,具体使用的时候再根据key来返回对应的lock.这是几个实现里最简单,性能最高,也是最终被采用的锁策略,代码如下: /** * 分

  • 五种JAVA GUI布局管理的方式

    1. 流式布局(FlowLayout) 定义: 通俗地说,流式布局就是根据窗口大小,自动改变窗口内组件的位置.例如:原窗口大小一行可以容纳10个BUTTON,但将窗口缩小后,每行仅能容纳5个BUTTON,此时原先的10个BUTTON中的五个就会自动排列到下一行. 示例:(省略panel的使用) Hashset package 布局管理; ​ import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.

  • 两种java实现二分查找的方式

    目录 1.二分查找算法思想 2.二分查找图示说明 3.二分查找优缺点 3.java代码实现 3.1 使用递归实现 3.1 不使用递归实现(while循环) 3.3 测试 4.时间复杂度 5.空间复杂度 起初在数据结构中学习递归时实现二分查找,实际上不用递归也可以实现,毕竟递归是需要开辟额外的空间的来辅助查询.本文就介绍两种方法 1.二分查找算法思想 有序的序列,每次都是以序列的中间位置的数来与待查找的关键字进行比较,每次缩小一半的查找范围,直到匹配成功. 一个情景:将表中间位置记录的关键字与查找

  • 一起聊聊Java中13种锁的实现方式

    目录 1.悲观锁 2.乐观锁 3.分布式锁 加锁 4.可重入锁 5.自旋锁 6.独享锁 7.共享锁 8.读锁/写锁 9.公平锁/非公平锁 10.可中断锁/不可中断锁 11.分段锁 12.锁升级(无锁|偏向锁|轻量级锁|重量级锁) 无锁 偏向锁 轻量级锁 重量级锁 13.锁优化技术(锁粗化.锁消除) 最近有很多小伙伴给我留言,分布式系统时代,线程并发,资源抢占,"锁" 慢慢变得很重要.那么常见的锁都有哪些? 今天Tom哥就和大家简单聊聊这个话题. 1.悲观锁 正如其名,它是指对数据修改时

  • Java分布式锁的三种实现方案

    方案一:数据库乐观锁 乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的left_count减1,在并发的情况下如何要保证left_count不为负数,乐观锁的实现方式为在红包表上添加一个版本号字段(version),默认为0. 异常实现流程 -- 可能会发生的异常情况 -- 线程1查询,当前left_count为1,则有记录 select * from t_bonus

  • 很多人竟然不知道Java线程池的创建方式有7种

    目录 前言 什么是线程池? 线程池使用 1.FixedThreadPool 2.CachedThreadPool 3.SingleThreadExecutor 4.ScheduledThreadPool 5.SingleThreadScheduledExecutor 6.newWorkStealingPool 7.ThreadPoolExecutor 线程池的执行流程 线程拒绝策略 自定义拒绝策略 究竟选用哪种线程池? 前言 根据摩尔定律所说:集成电路上可容纳的晶体管数量每 18 个月翻一番,因

  • java 中锁的性能提高办法

    java 中锁的性能提高办法 我们努力为自己的产品所遇到的问题思考解决办法,但在这篇文章中我将给大家分享几种常用的技术,包括分离锁.并行数据结构.保护数据而非代码.缩小锁的作用范围,这几种技术可以使我们不使用任何工具来检测死锁. 锁不是问题的根源,锁之间的竞争才是 通常在多线程的代码中遇到性能方面的问题时,一般都会抱怨是锁的问题.毕竟锁会降低程序的运行速度和其较低的扩展性是众所周知的.因此,如果带着这种"常识"开始优化代码,其结果很有可能是在之后会出现讨人厌的并发问题. 因此,明白竞争

  • 浅谈对java中锁的理解

    在并发编程中,经常遇到多个线程访问同一个 共享资源 ,这时候作为开发者必须考虑如何维护数据一致性,在java中synchronized关键字被常用于维护数据一致性.synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的,因为对于共享资源属性访问是必要也是必须的,下文会有具体示例演示. 一.java中的锁 一般在java中所说的锁就是指的内置锁,每个java对象都可以作为一个实现同步的锁,虽然说在java中一切皆对象, 但是锁

  • 9种Java单例模式详解(推荐)

    单例模式的特点 一个类只允许产生一个实例化对象. 单例类构造方法私有化,不允许外部创建对象. 单例类向外提供静态方法,调用方法返回内部创建的实例化对象.  懒汉式(线程不安全) 其主要表现在单例类在外部需要创建实例化对象时再进行实例化,进而达到Lazy Loading 的效果. 通过静态方法 getSingleton() 和private 权限构造方法为创建一个实例化对象提供唯一的途径. 不足:未考虑到多线程的情况下可能会存在多个访问者同时访问,发生构造出多个对象的问题,所以在多线程下不可用这种

  • Java 自旋锁(spinlock)相关知识总结

    一.前言 谈到『自旋锁』,可能大家会说,这有啥好讲的,不就是等待资源的线程"原地打转"嘛.嗯,字面理解的意思很到位,但能深入具体点吗?自旋锁的设计真就这么简单? 本文或者说本系列的目的,都是让大家不要停留在表面,而是深入分析,做到: 灵活使用 掌握原理 优缺点 二.锁的优化:自旋锁 当多个线程想同时访问同一个资源时,就存在资源冲突,这时,大家最直接想到的就是加锁来互斥访问,加锁会有这么几个问题: 等待资源的线程进入睡眠,发生用户态向内核态的切换,有一定的性能开销: 占用资源的线程很快就

随机推荐