Google Guava Striped 实现细粒度锁
首先不谈Striped能做什么,我们来看下如下的代码
https://my.oschina.net/lis1314/blog/664142?fromerr=8CDQbye9
/*** 购买产品* @param user 用户* @param buyAmount 购买金额* @param productId 产品编号*/public static void buy(String user,Integer buyAmount,String productId){System.out.println(user+":开始购买【"+productId+"】的产品");Product product = DB.getProduct(productId);if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){int residual = product.getTotalAmount() - buyAmount;product.setTotalAmount(residual);//更新数据库System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】");}else{System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】");}} public static void main(String[] args) {String user1 = "张三";buy(user1, 10000, "1");
}
/*** 销售产品* @author lis*/
public class Product {/** ID */private String id;/** 总价值 ,每个产品的价值为1W */private Integer totalAmount = 10000;//省略getter..setter
}
运行结果
张三:开始购买【1】的产品
张三:成功购买【1】产品,产品剩余价值为【0】
我想大家能够立即看出来,这段代码是有问题的,非线程安全的。
假如同时有两个用户发起购买,一定会出现线程安全问题。
这里我就不在验证了,那么修改buy方法代码结构如下
//在buy方法上加了synchronized,同时使当前线程睡眠5秒/*** 购买产品* @param user 用户* @param buyAmount 购买金额* @param productId 产品编号*/
public synchronized static void buy(String user,Integer buyAmount,String productId)throws Exception{System.out.println(user+":开始购买【"+productId+"】的产品");Thread.sleep(5000);//睡眠5秒Product product = DB.getProduct(productId);if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){int residual = product.getTotalAmount() - buyAmount;product.setTotalAmount(residual);//更新数据库System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】");}else{System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】");}}
main方法修改如下
public static void main(String[] args) {//运行开始时间long startTime = System.currentTimeMillis();//这个类主要是,使多个线程同时进行工作,如果不了解建议网上搜索相关的文章进行学习final CyclicBarrier barrier = new CyclicBarrier(2);//不限制大小的线程池ExecutorService pool = Executors.newCachedThreadPool();final String user1 = "张三";final String user2 = "李四";pool.execute(new Runnable() {@Overridepublic void run() {try {barrier.await();buy(user1, 10000, "1");} catch (Exception e) {e.printStackTrace();}}});pool.execute(new Runnable() {@Overridepublic void run() {try {barrier.await();buy(user2, 10000, "2");} catch (Exception e) {e.printStackTrace();}}});pool.shutdown();while (!pool.isTerminated()) { }System.out.println("运行时间为:【"+TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - startTime))+"】秒");}
运行结果
李四:开始购买【2】的产品
李四:成功购买【2】产品,产品剩余价值为【0】
张三:开始购买【1】的产品
张三:成功购买【1】产品,产品剩余价值为【0】
运行时间为:【10】秒
从运行结果不难看出,线程是安全了,但是运行效率降低了,众所周知,在一个方法上加锁,那么锁的粒度太大了。我们能不能对
销售产品的ID进行加锁呢?
比如这样修改buy方法?
/*** 购买产品* @param user 用户* @param buyAmount 购买金额* @param productId 产品编号*/public static void buy(String user,Integer buyAmount,String productId)throws Exception{synchronized(productId){System.out.println(user+":开始购买【"+productId+"】的产品");TimeUnit.SECONDS.sleep(5);//使当前线程睡眠5秒Product product = DB.getProduct(productId);if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){int residual = product.getTotalAmount() - buyAmount;product.setTotalAmount(residual);//更新数据库System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】");}else{System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】");}}}
运行结果李四:开始购买【2】的产品
张三:开始购买【1】的产品
李四:成功购买【2】产品,产品剩余价值为【0】
张三:成功购买【1】产品,产品剩余价值为【0】
运行时间为:【5】秒
时间立即缩短了,想想如果2个用户购买的是1个产品,这样能够锁定么?运行时间是5秒还是10秒?
那么我们修改main方法中的两个线程,产品ID相同buy(user2, 10000, "1");,这里就不在贴代码了
运行结果
李四:开始购买【1】的产品
李四:成功购买【1】产品,产品剩余价值为【0】
张三:开始购买【1】的产品
张三:购买【1】产品失败,产品剩余价值为【0】
运行时间为:【10】秒
居然同步成功了,那么这个方法是不是就解决了不同产品之间,非同一条数据,就能够降低锁的粒度,同时提高程序的性能问题呢?
那么我们在对main方法中的buy方法调度进行修改:buy(user2, 10000, new String("1"));
运行结果
李四:开始购买【1】的产品
张三:开始购买【1】的产品
李四:成功购买【1】产品,产品剩余价值为【0】
张三:成功购买【1】产品,产品剩余价值为【0】
运行时间为:【5】秒
看到这个结果...很明显失败了,这不是我们想要的结果。。
那么为什么在没有使用new之前是可以进行数据同步的呢?众所周知,synchronized是对象锁,它锁定的堆内存地址在JVM中一定是唯一的。之前之所以没有问题,是因为String的常量池机制,这个如果不清楚...建议搜索相关文章自学补脑
既然上述的形式不行,那么我们怎么降低锁的粒度,达到ID不一样则锁不会冲突呢?
-------------------------------------------------------
那么下面隆重介绍google guava的Striped这个类了
它的底层实现是ConcurrentHashMap,它的原理参照:http://blog.csdn.net/liuzhengkang/article/details/2916620
Striped主要是保证,传递对象的hashCode一致,返回相同对象的锁,或者信号量
但是它不能保证对象的hashCode不一致,则返回的Lock未必不是同一个。
如果想降低这种概率发生,可以调整stripes的数值,数值越高发生的概率越低。
不难理解,之所以会出现这种问题完全取决于缓存锁的大小,我个人是这么理解的,如有错误请批评指正,相互学习!
它可以获取如下两种类型:
java.util.concurrent.locks.Lock
java.util.concurrent.Semaphore
这里我介绍下Lock,而不说Semaphore。
创建一个强引用的Striped<Lock>
com.google.common.util.concurrent.Striped.lock(int)
创建一个弱引用的Striped<Lock>
com.google.common.util.concurrent.Striped.lazyWeakLock(int)
上面的两个方法等同于它的构造方法
那么如何理解它所谓的强和弱呢?
我个人是这么理解的:它的强和弱等同于Java中的强引用和弱引用,强则为不回收,弱则为在JVM执行垃圾回收时立即回收。
我在实际的工作中使用的是弱引用:考虑到有大量的数据,不可能每条数据的hashCode都进行缓存(对应的锁),所以我使用弱引用,当然,如果是比较固定的几个hashCode(理解为对象的唯一标识,如ID),那么可以使用强引用。
那么下面直接上代码:究竟如何用这个玩意,修改之前的buy方法
//创建一个弱引用的Striped<Lock>private static final Striped<Lock> striped = Striped.lazyWeakLock(127);/*** 购买产品* @param user 用户* @param buyAmount 购买金额* @param productId 产品编号*/public static void buy(String user,Integer buyAmount,String productId)throws Exception{Lock lock = striped.get(productId);//获取锁try{lock.lock();//锁定System.out.println(user+":开始购买【"+productId+"】的产品");TimeUnit.SECONDS.sleep(5);//使当前线程睡眠5秒Product product = DB.getProduct(productId);if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){int residual = product.getTotalAmount() - buyAmount;product.setTotalAmount(residual);//更新数据库System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】");}else{System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】");}}finally{lock.unlock();//释放锁}}
运行结果:相同ID的销售产品
张三:开始购买【1】的产品
张三:成功购买【1】产品,产品剩余价值为【0】
李四:开始购买【1】的产品
李四:购买【1】产品失败,产品剩余价值为【0】
运行时间为:【10】秒
---------------------------------------------------------------------
运行结果:不同ID的销售产品
李四:开始购买【2】的产品
张三:开始购买【1】的产品
张三:成功购买【1】产品,产品剩余价值为【0】
李四:成功购买【2】产品,产品剩余价值为【0】
运行时间为:【5】秒
那么我们之前想要的效果达到了。。。
---------------------------------------------------完整代码------------------------------------------------------
package com.lis.guava.study;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import com.google.common.util.concurrent.Striped;
public class Operator {public static void main(String[] args) {//运行开始时间long startTime = System.currentTimeMillis();//这个类主要是,使多个线程同时进行工作,如果不了解建议网上搜索相关的文章进行学习final CyclicBarrier barrier = new CyclicBarrier(2);//不限制大小的线程池ExecutorService pool = Executors.newCachedThreadPool();final String user1 = "张三";final String user2 = "李四";pool.execute(new Runnable() {@Overridepublic void run() {try {barrier.await();buy(user1, 10000, new String("1"));} catch (Exception e) {e.printStackTrace();}}});pool.execute(new Runnable() {@Overridepublic void run() {try {barrier.await();//buy(user2, 10000, new String("2"));buy(user2, 10000, new String("1"));} catch (Exception e) {e.printStackTrace();}}});pool.shutdown();while (!pool.isTerminated()) { }System.out.println("运行时间为:【"+TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - startTime))+"】秒");}//创建一个弱引用的Striped<Lock>private static final Striped<Lock> striped = Striped.lazyWeakLock(100);/*** 购买产品* @param user 用户* @param buyAmount 购买金额* @param productId 产品编号*/public static void buy(String user,Integer buyAmount,String productId)throws Exception{Lock lock = striped.get(productId);//获取锁try{lock.lock();//锁定System.out.println(user+":开始购买【"+productId+"】的产品");TimeUnit.SECONDS.sleep(5);//使当前线程睡眠5秒Product product = DB.getProduct(productId);if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){int residual = product.getTotalAmount() - buyAmount;product.setTotalAmount(residual);//更新数据库System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】");}else{System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】");}}finally{lock.unlock();//释放锁}}}
package com.lis.guava.study;
import java.util.HashMap;
import java.util.Map;
/*** 模拟DataBase* @author lis**/
public class DB {private static Map<String, Product> products = new HashMap<>();static {// 初始化数据products.put("1", new Product("1"));products.put("2", new Product("2"));}public static Product getProduct(String productId) {return products.get(productId);}
}
package com.lis.guava.study;
/*** 销售产品* @author lis*/
public class Product {/** ID */private String id;/** 总价值 ,每个产品的价值为10W */private Integer totalAmount = 10000;public Product(String id) {this.id = id;}public String getId() {return id;}public void setId(String id) {this.id = id;}public Integer getTotalAmount() {return totalAmount;}public void setTotalAmount(Integer totalAmount) {this.totalAmount = totalAmount;}
}
-------------------------------------------------------------------------------------------------------------------
Striped我就介绍到这里,感兴趣的童鞋可以自己研究下它底层是如何实现的。
我的观点未必正确,如有错误,十分希望各位童鞋能够批评指正,相互学习、相互进步!!!
Google Guava Striped 实现细粒度锁相关推荐
- 使用 Google Guava Striped 实现基于 Key 的并发锁
写 Java 代码至今,在应对可能冲突的共享资源操作时会尽量用 JDK 1.5 开始引入的并发锁(如 Lock 的各类实现类, ReentrantLock 等) 进行锁定,而不是原来的 synchro ...
- 关于小概率锁碰撞的细粒度锁方案
文章目录 前言 锁的细粒度级别 基于小概率锁碰撞的lock pool实现方案 引用 前言 在分布式系统中,我们常常使用锁来保证操作的一致性控制.但是锁的存在则意味着必然存在着锁竞争的情况.而且这种竞争 ...
- Google Guava,牛逼的脚手架
01.前世今生 你好呀,我是 Guava. 1995 年的时候,我的"公明"哥哥--Java 出生了.经过 20 年的发展,他已经成为世界上最流行的编程语言了,请允许我有失公允的把 ...
- 【Guava】Google Guava本地高效缓存
1.Google,Guava本地高效缓存 Guva是google开源的一个公共java库,类似于Apache Commons,它提供了集合,反射,缓存,科学计算,xml,io等一些工具类库.cache ...
- Google,Guava本地高效缓存
Guva是google开源的一个公共java库,类似于Apache Commons,它提供了集合,反射,缓存,科学计算,xml,io等一些工具类库. cache只是其中的一个模块.使用Guva cac ...
- Google guava第一讲:guava缓存实战/使用场景/缓存清理/最佳实践/caffeine实战
Guava缓存实战及使用场景 摘要:本文是Google guava 第一件,本文先介绍了为什么使用Guava Cache缓存,然后讲解了使用方法及底层数据结构,结合实际业务,讲解使用guava过程中踩 ...
- Google Guava Cache高效本地缓存
目录 Guava Cache使用需求和场景 需求 场景 缓存设置 缓存的并发级别 缓存的初始容量设置 设置最大存储 缓存清除策略 基于存活时间的清除策略 基于容量的清除策略 基于权重的清除 策略 显式 ...
- 【编程实践】Google Guava 极简教程
前言 Guava 工程包含了若干被 Google 的 Java 项目广泛依赖 的核心库,我们希望通过此文档为 Guava 中最流行和最强大的功能,提供更具可读性和解释性的说明. 适用人群 本教程是基础 ...
- Error:Could not download guava.jar (com.google.guava:guava:19.0): No cached version available for of
今天从git导入demo 报错 Error:Could not download guava.jar (com.google.guava:guava:19.0): No cached version ...
最新文章
- 使用HTML CSS完成初步的页面,任务九:使用HTML/CSS实现一个复杂页面(示例代码)
- Spring Cloud Kubernetes 指南
- 产品生涯你无法躲开的设计:微信授权登录
- BZOJ 4849 [NEERC2016] Mole Tunnels (模拟费用流)
- 论文理解 R-FCN:基于区域的全卷积网络来检测物体
- 微课|中学生可以这样学Python(例11.2):tkinter猜数游戏(3)
- C# 最小化到系统托盘的实现(一)
- 系统结构目录与正则表达式
- 【递归】剑指offer——面试题19:二叉树的镜像
- iPad 读不到 USB,在 Mac 上如何进行 USB 格式化?
- wdcp如何修改phpmyadmin导入 最大限制2048 KB
- Android Studio查看Android源码
- 项目进度相关计算总结
- 线性代数之 矩阵的迹
- 买腾讯云服务器怎么选择
- wordpress教程
- VC浏览器相关的学习(六)(IDispEventImpl包装的主要方法)
- MATLAB与STK互联13:卫星对象操作(4)—三维显示
- 简易图书管理系统(主要是jsp+servlet的练习),基于jsp+servlet的图书管理系统
- 强化学习keras-rl2的安装注意点
热门文章
- 沧浪之水清兮,可以濯吾缨;沧浪之水浊兮,可以濯吾足
- “快充”还是感觉慢,到底是什么原因?
- python常见运算符
- unity3d-unet小demo
- 鼠标移入图片高亮,其余颜色变暗
- 江苏省计算机二级高级office知识点,计算机二级高级Office常见知识点积累
- Mapbox之栅格矢量瓦片
- c语言撩妹小程序,撩妹简单的web小程序!分享给大家~~~~~~
- 测试POST传输工具【poster】。
- 转载:微信Windows版-无效的wechatwin.dll文件errcode:126,点击“确定”下载最新版本