首先不谈Striped能做什么,我们来看下如下的代码
/** * 购买产品 * @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() { @Override public void run() { try { barrier.await(); buy(user1, 10000, "1"); } catch (Exception e) { e.printStackTrace(); } } }); pool.execute(new Runnable() { @Override public 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() { @Override public void run() { try { barrier.await(); buy(user1, 10000, new String("1")); } catch (Exception e) { e.printStackTrace(); } } }); pool.execute(new Runnable() { @Override public 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我就介绍到这里,感兴趣的童鞋可以自己研究下它底层是如何实现的。
我的观点未必正确,如有错误,十分希望各位童鞋能够批评指正,相互学习、相互进步!!!