Google Guava Striped 实现细粒度锁

首先不谈Striped能做什么,我们来看下如下的代码

1/** 2  * 购买产品 3  * @param user 用户 4  * @param buyAmount 购买金额 5  * @param productId 产品编号 6  */ 7 public static void buy(String user,Integer buyAmount,String productId){ 8  System.out.println(user+":开始购买【"+productId+"】的产品"); 9  Product product = DB.getProduct(productId); 10  if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){ 11   int residual = product.getTotalAmount() - buyAmount; 12   product.setTotalAmount(residual);//更新数据库 13   System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】"); 14  }else{ 15   System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】"); 16  } 17 }  18  19public static void main(String[] args) { 20  String user1 = "张三"; 21  buy(user1, 10000, "1"); 22} 23/** 24 * 销售产品 25 * @author lis 26 */ 27public class Product { 28    /** ID */ 29    private String id; 30    /** 总价值 ,每个产品的价值为1W */ 31    private Integer totalAmount = 10000; 32    //省略getter..setter 33} 34 35运行结果 36张三:开始购买【1】的产品 37张三:成功购买【1】产品,产品剩余价值为【038我想大家能够立即看出来,这段代码是有问题的,非线程安全的。 39假如同时有两个用户发起购买,一定会出现线程安全问题。 40这里我就不在验证了,那么修改buy方法代码结构如下 41 42//在buy方法上加了synchronized,同时使当前线程睡眠5秒 43 44/** 45  * 购买产品 46  * @param user 用户 47  * @param buyAmount 购买金额 48  * @param productId 产品编号 49  */ 50public synchronized static void buy(String user,Integer buyAmount,String productId)throws Exception{ 51  System.out.println(user+":开始购买【"+productId+"】的产品"); 52  Thread.sleep(5000);//睡眠5秒 53  Product product = DB.getProduct(productId); 54  if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){ 55   int residual = product.getTotalAmount() - buyAmount; 56   product.setTotalAmount(residual);//更新数据库 57   System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】"); 58  }else{ 59   System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】"); 60  } 61 }

main方法修改如下

1public static void main(String[] args) { 2  //运行开始时间 3  long startTime = System.currentTimeMillis(); 4  //这个类主要是,使多个线程同时进行工作,如果不了解建议网上搜索相关的文章进行学习 5  final CyclicBarrier barrier = new CyclicBarrier(2); 6  //不限制大小的线程池 7  ExecutorService pool = Executors.newCachedThreadPool(); 8  final String user1 = "张三"; 9  final String user2 = "李四"; 10  pool.execute(new Runnable() { 11   @Override 12   public void run() { 13    try { 14     barrier.await(); 15     buy(user1, 10000, "1"); 16    } catch (Exception e) { 17     e.printStackTrace(); 18    } 19   } 20  }); 21  pool.execute(new Runnable() { 22   @Override 23   public void run() { 24    try { 25     barrier.await(); 26     buy(user2, 10000, "2"); 27    } catch (Exception e) { 28     e.printStackTrace(); 29    } 30   } 31  }); 32  pool.shutdown(); 33  while (!pool.isTerminated()) {   34  } 35  System.out.println("运行时间为:【"+TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - startTime))+"】秒"); 36 } 37 38运行结果 39李四:开始购买【2】的产品 40李四:成功购买【2】产品,产品剩余价值为【041张三:开始购买【1】的产品 42张三:成功购买【1】产品,产品剩余价值为【043运行时间为:【10】秒

从运行结果不难看出,线程是安全了,但是运行效率降低了,众所周知,在一个方法上加锁,那么锁的粒度太大了。我们能不能对

销售产品的ID进行加锁呢?

比如这样修改buy方法?

1/** 2  * 购买产品 3  * @param user 用户 4  * @param buyAmount 购买金额 5  * @param productId 产品编号 6  */ 7 public static void buy(String user,Integer buyAmount,String productId)throws Exception{ 8  synchronized(productId){ 9   System.out.println(user+":开始购买【"+productId+"】的产品"); 10   TimeUnit.SECONDS.sleep(5);//使当前线程睡眠5秒 11   Product product = DB.getProduct(productId); 12   if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){ 13    int residual = product.getTotalAmount() - buyAmount; 14    product.setTotalAmount(residual);//更新数据库 15    System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】"); 16   }else{ 17    System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】"); 18   } 19  } 20 } 21 22运行结果李四:开始购买【2】的产品 23张三:开始购买【1】的产品 24李四:成功购买【2】产品,产品剩余价值为【025张三:成功购买【1】产品,产品剩余价值为【026运行时间为:【5】秒

时间立即缩短了,想想如果2个用户购买的是1个产品,这样能够锁定么?运行时间是5秒还是10秒?

那么我们修改main方法中的两个线程,产品ID相同buy(user2, 10000, "1");,这里就不在贴代码了

1运行结果 2李四:开始购买【1】的产品 3李四:成功购买【1】产品,产品剩余价值为【04张三:开始购买【1】的产品 5张三:购买【1】产品失败,产品剩余价值为【06运行时间为:【10】秒

居然同步成功了,那么这个方法是不是就解决了不同产品之间,非同一条数据,就能够降低锁的粒度,同时提高程序的性能问题呢?

那么我们在对main方法中的buy方法调度进行修改:buy(user2, 10000, new String("1"));

1运行结果 2李四:开始购买【1】的产品 3张三:开始购买【1】的产品 4李四:成功购买【1】产品,产品剩余价值为【05张三:成功购买【1】产品,产品剩余价值为【06运行时间为:【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执行垃圾回收时立即回收。

这里我使用的是弱引用,当JVM内存不够时,回收这些锁。

那么下面直接上代码:究竟如何用这个玩意,修改之前的buy方法

1//创建一个弱引用的Striped<Lock> 2 private static final Striped<Lock> striped = Striped.lazyWeakLock(127); 3 /** 4  * 购买产品 5  * @param user 用户 6  * @param buyAmount 购买金额 7  * @param productId 产品编号 8  */ 9 public static void buy(String user,Integer buyAmount,String productId)throws Exception{ 10  Lock lock = striped.get(productId);//获取锁 11  try{ 12   lock.lock();//锁定 13   System.out.println(user+":开始购买【"+productId+"】的产品"); 14   TimeUnit.SECONDS.sleep(5);//使当前线程睡眠5秒 15   Product product = DB.getProduct(productId); 16   if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){ 17    int residual = product.getTotalAmount() - buyAmount; 18    product.setTotalAmount(residual);//更新数据库 19    System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】"); 20   }else{ 21    System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】"); 22   } 23  }finally{ 24   lock.unlock();//释放锁 25  } 26 } 27 28运行结果:相同ID的销售产品 29张三:开始购买【1】的产品 30张三:成功购买【1】产品,产品剩余价值为【031李四:开始购买【1】的产品 32李四:购买【1】产品失败,产品剩余价值为【033运行时间为:【10】秒  34--------------------------------------------------------------------- 35运行结果:不同ID的销售产品 36李四:开始购买【2】的产品 37张三:开始购买【1】的产品 38张三:成功购买【1】产品,产品剩余价值为【039李四:成功购买【2】产品,产品剩余价值为【040运行时间为:【5】秒

那么我们之前想要的效果达到了。。。

---------------------------------------------------完整代码------------------------------------------------------

1package com.lis.guava.study; 2import java.util.concurrent.CyclicBarrier; 3import java.util.concurrent.ExecutorService; 4import java.util.concurrent.Executors; 5import java.util.concurrent.TimeUnit; 6import java.util.concurrent.locks.Lock; 7import com.google.common.util.concurrent.Striped; 8public class Operator { 9  10 public static void main(String[] args) { 11  //运行开始时间 12  long startTime = System.currentTimeMillis(); 13  //这个类主要是,使多个线程同时进行工作,如果不了解建议网上搜索相关的文章进行学习 14  final CyclicBarrier barrier = new CyclicBarrier(2); 15  //不限制大小的线程池 16  ExecutorService pool = Executors.newCachedThreadPool(); 17  final String user1 = "张三"; 18  final String user2 = "李四"; 19  pool.execute(new Runnable() { 20   @Override 21   public void run() { 22    try { 23     barrier.await(); 24     buy(user1, 10000, new String("1")); 25    } catch (Exception e) { 26     e.printStackTrace(); 27    } 28   } 29  }); 30  pool.execute(new Runnable() { 31   @Override 32   public void run() { 33    try { 34     barrier.await(); 35     //buy(user2, 10000, new String("2")); 36     buy(user2, 10000, new String("1")); 37    } catch (Exception e) { 38     e.printStackTrace(); 39    } 40   } 41  }); 42  pool.shutdown(); 43  while (!pool.isTerminated()) {   44  } 45  System.out.println("运行时间为:【"+TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - startTime))+"】秒"); 46 } 47  48 //创建一个弱引用的Striped<Lock> 49 private static final Striped<Lock> striped = Striped.lazyWeakLock(100); 50 /** 51  * 购买产品 52  * @param user 用户 53  * @param buyAmount 购买金额 54  * @param productId 产品编号 55  */ 56 public static void buy(String user,Integer buyAmount,String productId)throws Exception{ 57  Lock lock = striped.get(productId);//获取锁 58  try{ 59   lock.lock();//锁定 60   System.out.println(user+":开始购买【"+productId+"】的产品"); 61   TimeUnit.SECONDS.sleep(5);//使当前线程睡眠5秒 62   Product product = DB.getProduct(productId); 63   if(product.getTotalAmount() > 0 && product.getTotalAmount() >= buyAmount){ 64    int residual = product.getTotalAmount() - buyAmount; 65    product.setTotalAmount(residual);//更新数据库 66    System.out.println(user+":成功购买【"+productId+"】产品,产品剩余价值为【"+residual+"】"); 67   }else{ 68    System.out.println(user+":购买【"+productId+"】产品失败,产品剩余价值为【"+product.getTotalAmount()+"】"); 69   } 70  }finally{ 71   lock.unlock();//释放锁 72  } 73 } 74  75}  76package com.lis.guava.study; 77import java.util.HashMap; 78import java.util.Map; 79/** 80 * 模拟DataBase 81 * @author lis 82 * 83 */ 84public class DB { 85  86 private static Map<String, Product> products = new HashMap<>(); 87 static { 88  // 初始化数据 89  products.put("1", new Product("1")); 90  products.put("2", new Product("2")); 91 } 92 public static Product getProduct(String productId) { 93  return products.get(productId); 94 } 95} 96package com.lis.guava.study; 97/** 98 * 销售产品 99 * @author lis 100 */ 101public class Product { 102 /** ID */ 103 private String id; 104 /** 总价值 ,每个产品的价值为10W */ 105 private Integer totalAmount = 10000; 106 public Product(String id) { 107  this.id = id; 108 } 109 public String getId() { 110  return id; 111 } 112 public void setId(String id) { 113  this.id = id; 114 } 115 public Integer getTotalAmount() { 116  return totalAmount; 117 } 118 public void setTotalAmount(Integer totalAmount) { 119  this.totalAmount = totalAmount; 120 } 121}

-------------------------------------------------------------------------------------------------------------------

Striped我就介绍到这里,感兴趣的童鞋可以自己研究下它底层是如何实现的。

我的观点未必正确,如有错误,十分希望各位童鞋能够批评指正,相互学习、相互进步!!!

点赞
收藏

评论区

加载中...

相关推荐

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

utils.js 文件 工具类 方法分享

javascript/时间解析工具@param{(Object|string|number)}time@param{string}cFormat@returns{string|null}/exportfunctionparseTime(time,cFormat){if(arguments.leng

java 处理emoji表情

public class EmojiUtil {/  将str中的emoji表情转为byte数组    @param str  @return /public static String resolveToByteFromEmoji(String str

C# winform判断窗体是否已打开

Form1form;///<summary///开始检测///</summary///<paramname"sender"</param///<paramname"e"</param

Scala中的match(模式匹配)

/\\模式匹配\/caseclassClass1(param1:String,param2:String)caseclassClass2(param1:String)objectCase{defmain(args:Array\String\){//通过模式匹配进行条件判断valtest1:

Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2]

!(https://oscimg.oschina.net/oscnet/0c930a24d551e0970e306d79a64f7148999.gif)!(https://oscimg.oschina.net/oscnet/3bca3884ca653a179e799d56df36d4622b3.png)解决方法:<selectid"sel