写 Java 代码至今,在应对可能冲突的共享资源操作时会尽量用 JDK 1.5 开始引入的并发锁(如 Lock 的各类实现类, ReentrantLock 等) 进行锁定,而不是原来的 synchronized 关键字强硬低性能锁。

这里是应用 JDK 1.5  的 Lock 的基本操作步骤

private Lock lock = new ReentrantLock();
private void operate() {
    // 安全操作 ....
    lock.lock();
    try {
        // 对共享资源的操作 ...
    } finally {
        lock.unlock();
    }
}

如此,operate() 就是一个线程安全的方法,任何对它的调用都安排到了一个队列里等着。但有时候上锁需要考虑更细的粒度,下面是一个演示案例,引出第一个问题

一、为何需要细粒度锁

private void merge(String filePath, List<String> deltaLines) {

lock.lock();

try {

Path path = Paths.get(filePath);

List<String> fileLines = Files.exists(path) ? Files.readAllLines(path) : new ArrayList<>();

fileLines.addAll(deltaLines);

fileLines.sort(Comparator.naturalOrder());

Files.write(path, fileLines);

} catch (Exception ex) {

ex.printStackTrace();

} finally {

lock.unlock();

}

}

被保护的操作序列是读取原文件内容,合并新行并排序,再回写文件; 如果原文件不存在则生成新文件,并含有已排序的新行。如果不保护该系列操作,文件内容将会被不同线程相互覆盖,因为两个线程可能读入相同的内容再加上各自不同的新行写回,而不是全部内容叠加。

二、用 ConcurrentHashMap 改进的细粒度锁

如果继承采用与前面一样保证整个方法绝对安全的方式,效率上就会变得很差,因为无论是操作相同还是不同的文件,统统得排着队进行。而实际上只有是操作不同的文件(filePath 不同), 是允许并发的。这就引出了要对锁的粒度进一步细化,只在文件路径相同时才需要获取锁。有一种实现方式是为不一样的 filePath 创建各自的锁,用 ConcurrentHashMap 缓存起来,看接来的改进:

private Map<String, Lock> cachedLocks = new ConcurrentHashMap<>();

private void merge(String filePath, List<String> deltaLines) {

Lock lock = cachedLocks.computeIfAbsent(filePath, key -> new ReentrantLock());

lock.lock();

try {

Path path = Paths.get(filePath);

// ... 以下省略

} finally {

lock.unlock();

}

}

改进后的代码在应对并发性的性能是大大提高了,有一个问题是如果应用中要操作百万,千万个不同的文件,那么势必在内存中创建相应数量的锁实例,对内存将是个不小的负担。即使线程池大小只有几个的时候锁实例的数量也与文件个数相同,并且长时间不再使用的锁实例都无法被回收。进一步的优化也许可以采用弱引用,或定时清理长时间不使用的锁实例,而且要兼顾到避免瞬间高并发时生成大量锁实例耗用内存的情形。

三、采用 Guava Striped 实现细粒度锁

这儿提及到了锁实例量与线程池大小关系,所以可以考虑把创建的锁实例放到一个固定大小(如使用它的线程池大小)的 ConcurrentHashMap 中,比如创建锁时清除缓存中最早未使用的锁,这样做对内存不会产生负担,就是清理工作必须做到高效。其实这一思考惯性正好引出了今天的主角: Google Guava 库的 Striped 类,Guava 当前版本是 27.1, 在 Guava 库中 Striped 类仍然被标记为 @Beta 不稳定版本,所以使用它的一起后果自负(可能造成死锁:使用guava Striped中的lock导致线程死锁的问题分析,该文发表于 2016-11-19)。

Guava 对 @Beta 的解释见 https://github.com/google/guava#important-warnings, 标记为 @Beta 的类或方法会被随时修改甚至是移除,如果使用它再次作为类库发布的话强烈建议用 Guava Beta Checker 检测并确保不要用 @Beta 的类。可怜 Striped 自从 13.0 加入后直至今天的 27.1 都未转正。

还继续往下阅读吗?

先来感受一下怎么用 Striped,而后再来了解它的 API 和实现原理:

private Striped<Lock> stripedLocks = Striped.lock(20);

private void merge(String filePath, List<String> deltaLines) {

Lock lock = stripedLocks.get(filePath) ;

lock.lock();

try {

Path path = Paths.get(filePath);

// ... 以下省略

} finally {

lock.unlock();

}

}

看上去就是替代了我们用 ConcurrentHashMap 部分的代码,代码方法并没什么简洁,但是它省内存啊,不管不同的文件名有多少个就只要预建 20 个锁,当然 20 这个数字也是基于 merge 方法可能被多少个线程并发执行(如线程池的大小) 来设置的。

Striped 比用 ConcurrentHashMap 缓存的锁实例的好处是锁可被重用,Striped 中同一个锁第一次由 key1 引用,第二次还能被 key2 引用,ConcurrentHashMap 中的锁呢, key 1 用过的就不再被 key2 再次使用。

Striped 实现细粒度锁是基于它自己在 Striped Javadoc 中提出的一个真理,简单说来就以下三条

  1. 相同的 key (hashCode()/equals()) 时, striped.get(key) 总会得到相同的锁实例
  2. 但是不同的 key 却可能调用 striped.get(key) 获得相同的锁实例
  3. 基于上一条,预建更多的锁实例数量能减低锁碰撞的可能性

第一条保证被保护的代码是线程安全的,第二条会出现不同 key 的两个任务会排在同一个队列上,性能上会有所降低,但能够在锁数量(内存)与并发规模之间平衡。比如线程池大小为 20,预建 80 个锁对内存来说毫无压力,比瞬间百万,千万个锁好多了。Guava 建议是,对于计算密集型的任务创建 4 倍于可用处理器数目的锁。

紧接着来看下 Striped 提供的 API,它支持创建 Lock, Semaphore 和 ReadWriteLock,并且提供创建 eager 和 lazyWeak 两个版本

  1. public static Striped<Lock> lock(int strips)
  2. public static Striped<Lock> lazyWeakLock(int stripes)
  3. public static Striped<Semaphore> semaphore(int stripes, int permits)
  4. public static Striped<Semaphore> lazyWeakSemaphore(int stripes, int permits)
  5. public static Striped<ReadWriteLock> readWriteLock(int stripes)
  6. public static Striped<ReadWriteLock> lazyWeakReadWriteLock(int stripes)

以上返回的 Lock 或 ReadWriteLock 都是可重入锁,lazyWeakXxx() 版本的选择也是基于节约内存的考虑,如果并发大小是可控且不大的情况不一定需要 lazyWeekXxx() 的版本,比如前面说的线程池大小为 20 的情况初始化 80 个锁直接用 Striped.lock(80) 就行。

对于 Striped 的使用也就差不多了,如果用 semaphore(...) 的话需要了解 JDK 中 Semaphore 信号量的使用,其实是在同一把锁的情况下次一层次的控制。举个例子,Lock 控制了同一个帐号只能同时一个地方登陆,Semaphore(信号量) 放宽一些,可以控制同一个帐号最多在几个地方同时登陆。

使用 Google Guava Striped 实现基于 Key 的并发锁相关推荐

  1. Google Guava Striped 实现细粒度锁

    首先不谈Striped能做什么,我们来看下如下的代码 https://my.oschina.net/lis1314/blog/664142?fromerr=8CDQbye9 /*** 购买产品* @p ...

  2. Java类库Google Guava学习

    参考 官网 https://github.com/google/guava Google Guava官方教程(中文版) | 并发编程网 – ifeve.com 一篇让你熟练掌握Google Guava ...

  3. 一致性Hash(基于google Guava实现)

    背景 一般我们使用的hash就是md5 sha 之类的工具类,在负载均衡会要求类似同一个ip在增加节点时还是定位到之前的节点,这时就要用到一致性hash.具体实现代码参考(基于google Guava ...

  4. Google Guava Collections 使用介绍

    原帖http://www.open-open.com/lib/view/open1325143343733.html 简介: Google Guava Collections 是一个对 Java Co ...

  5. [Google Guava] 3-缓存

    原文地址  译文地址    译者:许巧辉  校对:沈义扬 范例 01 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() ...

  6. Google Guava BloomFilter

    当Guava项目发布版本11.0时,新添加的功能之一是BloomFilter类. BloomFilter是唯一的数据结构,用于指示元素是否包含在集合中. 使BloomFilter有趣的是,它将指示元素 ...

  7. 【Guava】Google Guava本地高效缓存

    1.Google,Guava本地高效缓存 Guva是google开源的一个公共java库,类似于Apache Commons,它提供了集合,反射,缓存,科学计算,xml,io等一些工具类库.cache ...

  8. Google,Guava本地高效缓存

    Guva是google开源的一个公共java库,类似于Apache Commons,它提供了集合,反射,缓存,科学计算,xml,io等一些工具类库. cache只是其中的一个模块.使用Guva cac ...

  9. Google Guava之--cache

    一.简介 Google Guava包含了Google的Java项目许多依赖的库,如:集合 [collections] .缓存 [caching] .原生类型支持 [primitives support ...

最新文章

  1. Python---哈夫曼树---Huffman Tree
  2. python非官方的二进制扩展包下载地址
  3. Oneproxy 读写分离
  4. Python3转义字符
  5. linux java 查询mysql_Linux Java连接MySQL数据库
  6. android 单元测试 多线程,多线程之单元测试(Junit)
  7. 证明LDU分解的唯一性
  8. 自建 bitwarden 密码管理服务
  9. Java 后台验证码汉字拼音校验
  10. 性能优化-图片压缩格式的选择(ETC和ASTC)
  11. 华为OD(外包)社招技术二面,总结复盘
  12. 提问的智慧 (How To Ask Questions The Smart Way)
  13. 电脑安装哪款linux系统好,四款linux操作系统总有一款适合你
  14. linux编译ace tao,ACE_TAO的编译
  15. 开发常用镜像站 - 阿里云镜像站
  16. 朋友圈图片评论功能,来了!
  17. 有谁知道怎么处理微信用户头像过期问题,除了本地保存,因为不会用七牛云远程附件
  18. 论二级域名收集的各种姿势
  19. wifi网络为什么总是断线 (by quqi99)
  20. 【NOIP2013提高组day1】货车运输

热门文章

  1. 系统中负负得正的兼容逻辑也许暂时能跑起来, 但迟早会坑人!
  2. Java修炼——手写服务器项目
  3. 通用表查询返回所有行(只适用于单表)
  4. 操作系统二轮复习(进程的同步与互斥)
  5. Python django 猫咪管理系统
  6. oracle中sql的递归查询运用
  7. HTML 表格合并(表格合并行属性 rowspan 将多行合并成一行)
  8. 关于软件产品化的几点思考【转】
  9. 虚拟示波器软件 JSCOPE -- 使用 jlink 仿真器来查看变量
  10. 计算机三维制图论文,三维重建初探(整理的一些资料及论文分享)