首先不谈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的数值,数值越高发生的概率越低。

不难理解,之所以会出现这种问题完全取决于缓存锁的大小,我个人是这么理解的,如有错误请批评指正,相互学习!

它可以获取如下两种类型:

  1. java.util.concurrent.locks.Lock

  2. 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 实现细粒度锁相关推荐

  1. 使用 Google Guava Striped 实现基于 Key 的并发锁

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

  2. 关于小概率锁碰撞的细粒度锁方案

    文章目录 前言 锁的细粒度级别 基于小概率锁碰撞的lock pool实现方案 引用 前言 在分布式系统中,我们常常使用锁来保证操作的一致性控制.但是锁的存在则意味着必然存在着锁竞争的情况.而且这种竞争 ...

  3. Google Guava,牛逼的脚手架

    01.前世今生 你好呀,我是 Guava. 1995 年的时候,我的"公明"哥哥--Java 出生了.经过 20 年的发展,他已经成为世界上最流行的编程语言了,请允许我有失公允的把 ...

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

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

  5. Google,Guava本地高效缓存

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

  6. Google guava第一讲:guava缓存实战/使用场景/缓存清理/最佳实践/caffeine实战

    Guava缓存实战及使用场景 摘要:本文是Google guava 第一件,本文先介绍了为什么使用Guava Cache缓存,然后讲解了使用方法及底层数据结构,结合实际业务,讲解使用guava过程中踩 ...

  7. Google Guava Cache高效本地缓存

    目录 Guava Cache使用需求和场景 需求 场景 缓存设置 缓存的并发级别 缓存的初始容量设置 设置最大存储 缓存清除策略 基于存活时间的清除策略 基于容量的清除策略 基于权重的清除 策略 显式 ...

  8. 【编程实践】Google Guava 极简教程

    前言 Guava 工程包含了若干被 Google 的 Java 项目广泛依赖 的核心库,我们希望通过此文档为 Guava 中最流行和最强大的功能,提供更具可读性和解释性的说明. 适用人群 本教程是基础 ...

  9. 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 ...

最新文章

  1. 使用HTML CSS完成初步的页面,任务九:使用HTML/CSS实现一个复杂页面(示例代码)
  2. Spring Cloud Kubernetes 指南
  3. 产品生涯你无法躲开的设计:微信授权登录
  4. BZOJ 4849 [NEERC2016] Mole Tunnels (模拟费用流)
  5. 论文理解 R-FCN:基于区域的全卷积网络来检测物体
  6. 微课|中学生可以这样学Python(例11.2):tkinter猜数游戏(3)
  7. C# 最小化到系统托盘的实现(一)
  8. 系统结构目录与正则表达式
  9. 【递归】剑指offer——面试题19:二叉树的镜像
  10. iPad 读不到 USB,在 Mac 上如何进行 USB 格式化?
  11. wdcp如何修改phpmyadmin导入 最大限制2048 KB
  12. Android Studio查看Android源码
  13. 项目进度相关计算总结
  14. 线性代数之 矩阵的迹
  15. 买腾讯云服务器怎么选择
  16. wordpress教程
  17. VC浏览器相关的学习(六)(IDispEventImpl包装的主要方法)
  18. MATLAB与STK互联13:卫星对象操作(4)—三维显示
  19. 简易图书管理系统(主要是jsp+servlet的练习),基于jsp+servlet的图书管理系统
  20. 强化学习keras-rl2的安装注意点

热门文章

  1. 沧浪之水清兮,可以濯吾缨;沧浪之水浊兮,可以濯吾足
  2. “快充”还是感觉慢,到底是什么原因?
  3. python常见运算符
  4. unity3d-unet小demo
  5. 鼠标移入图片高亮,其余颜色变暗
  6. 江苏省计算机二级高级office知识点,计算机二级高级Office常见知识点积累
  7. Mapbox之栅格矢量瓦片
  8. c语言撩妹小程序,撩妹简单的web小程序!分享给大家~~~~~~
  9. 测试POST传输工具【poster】。
  10. 转载:微信Windows版-无效的wechatwin.dll文件errcode:126,点击“确定”下载最新版本