大家好,我是哪吒。
上一篇提到了锁粒度的问题,使用“越细粒度的锁越好”,真的是这样吗?会不会产生一些其它问题?
下面还是以购买酱香拿铁为例:
@Datapublic class Coffee { // 酱香拿铁 private String name; // 库存 public Integer inventory; public ReentrantLock lock = new ReentrantLock();}
private static List<Coffee> coffeeList = generateCoffee();public static List<Coffee> generateCoffee(){ List<Coffee> coffeeList = new ArrayList<>(); coffeeList.add(new Coffee("酱香拿铁1", 100)); coffeeList.add(new Coffee("酱香拿铁2", 100)); coffeeList.add(new Coffee("酱香拿铁3", 100)); coffeeList.add(new Coffee("酱香拿铁4", 100)); coffeeList.add(new Coffee("酱香拿铁5", 100)); return coffeeList;}
// 随机获取n杯咖啡private static List<Coffee> getCoffees(int n) { if(n >= coffeeList.size()){ return coffeeList; } List<Coffee> randomList = Stream.iterate(RandomUtils.nextInt(n), i -> RandomUtils.nextInt(coffeeList.size())) .distinct()// 去重 .map(coffeeList::get)// 跟据上面取得的下标获取咖啡 .limit(n)// 截取前面 需要随机获取的咖啡 .collect(Collectors.toList()); return randomList;}
private static boolean buyCoffees(List<Coffee> coffees) { //存放所有获得的锁 List<ReentrantLock> locks = new ArrayList<>(); for (Coffee coffee : coffees) { try { // 获得锁3秒超时 if (coffee.lock.tryLock(3, TimeUnit.SECONDS)) { // 拿到锁之后,扣减咖啡库存 locks.add(coffee.lock); coffeeList = coffeeList.stream().map(x -> { // 购买了哪个,就减哪个 if (coffee.getName().equals(x.getName())) { x.inventory--; } return x; }).collect(Collectors.toList()); } else { locks.forEach(ReentrantLock::unlock); return false; } } catch (InterruptedException e) { } } locks.forEach(ReentrantLock::unlock); return true;}
public static void main(String[] args){ StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 通过parallel并行流,购买100次酱香拿铁,一次买2杯,统计成功次数 long success = IntStream.rangeClosed(1, 100).parallel() .mapToObj(i -> { List<Coffee> getCoffees = getCoffees(2); //Collections.sort(getCoffees, Comparator.comparing(Coffee::getName)); return buyCoffees(getCoffees); }) .filter(result -> result) .count(); stopWatch.stop(); System.out.println("成功次数:"+success); System.out.println("方法耗时:"+stopWatch.getTotalTimeSeconds()+"秒"); for (Coffee coffee : coffeeList) { System.out.println(coffee.getName()+"-剩余:"+coffee.getInventory()+"杯"); }}
耗时有点久啊,20多秒。
数据对不对?
数量也对不上,应该卖掉200杯才对,哪里出问题了?
果不其然,出问题了,产生了死锁。
线程 m 在等待的一个锁被线程 n 持有,线程 n 在等待的另一把锁被线程 m 持有。
让大家都先拿一样的酱香拿铁不就好了。让所有线程都先获取酱香拿铁1的锁,然后再获取酱香拿铁2的锁,这样就不会出问题了。
也就是在随机获取n杯咖啡后,对其进行排序即可。
// 通过parallel并行流,购买100次酱香拿铁,一次买2杯,统计成功次数long success = IntStream.rangeClosed(1, 100).parallel() .mapToObj(i -> { List<Coffee> getCoffees = getCoffees(2); // 根据咖啡名称进行排序 Collections.sort(getCoffees, Comparator.comparing(Coffee::getName)); return buyCoffees(getCoffees); }) .filter(result -> result) .count();
看来真的不是越细粒度的锁越好,真的会产生死锁问题。通过对酱香拿铁进行排序,解决了死锁问题,避免循环等待,效率也得到了提升。
本文链接:http://www.28at.com/showinfo-26-12735-0.html喝了100杯酱香拿铁,我开窍了
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: .Net析构函数再论(源码剖析)