zoukankan      html  css  js  c++  java
  • 使用锁的时候需要注意的坑

    1.锁定的使用不符合业务逻辑

    • 首先如果加的锁与业务逻辑不匹配,那么加的锁显然难以起到作用的
    @Slf4j
    public class InterestingDemo {
    
            volatile int a = 1;
            volatile int b = 1;
    
            public synchronized void add() {
                log.info("add start");
                for (int i = 0; i < 10000; i++) {
                    a++;
                    b++;
                }
                log.info("add done");
            }
    
            public  void compare() {
                log.info("compare start");
                for (int i = 0; i < 10000; i++) {
                    //a始终等于b吗?
                    if (a < b) {
                        log.info("a:{},b:{},{}", a, b, a > b);
                        //最后的a>b应该始终是false吗?
                    }
                }
                log.info("compare done");
            }
    
        public static void main(String[] args) {
            InterestingDemo interestingDemo = new InterestingDemo();
            new Thread(() -> interestingDemo.add()).start();
            new Thread(() -> interestingDemo.compare()).start();
        }
        
    }
    

    执行结果:

    19:50:24.767 [Thread-1] INFO com.cy.lock.InterestingDemo - compare start
    19:50:24.767 [Thread-0] INFO com.cy.lock.InterestingDemo - add start
    19:50:24.767 [Thread-0] INFO com.cy.lock.InterestingDemo - add done
    19:50:24.767 [Thread-1] INFO com.cy.lock.InterestingDemo - a:95,b:353,true
    19:50:24.767 [Thread-1] INFO com.cy.lock.InterestingDemo - compare done
    

    从代码的执行结果来看,这段代码显然没有按照我们期望的那些去执行,其中的原因就是我们只对add()加了锁,而没有对compare方法进行加锁。导致这两个方法并不能完全同步执行从而出错,其解决的方法也比较简单,只要再给compare()方法也加上锁就好了

    2.锁的层级与被保护对象的层级不一致

    处理锁的逻辑与业务逻辑不一致可能会导致锁失效以外,锁的层级与被保护对象的层级不一致也有可能导致锁的失效

    public class Data {
    
        private static int counter = 0;
        //private static Object locker = new Object();
    
        public static int reset() {
            counter = 0;
            return counter;
        }
    
        public synchronized void wrong() {
            counter ++;
    
        }
    
        public static int getCounter() {
            return counter;
        }
    
        public static void main(String[] args) {
            Data.reset();
            IntStream.rangeClosed(1, 10000).parallel().forEach(i -> {
                new Data().wrong();
            });
            System.out.println(Data.getCounter());
        }
    }
    

    执行结果:

    8548
    
    Process finished with exit code 0
    

    从执行结果来看,这个结果与我们所期望的10000不一致,说明程序运行期间出现了线程安全的问题,而这个问题的主要原因就是本来需要用锁保护的静态变量counter属于类,要采用类级别的锁才能保护,而非静态的getCounter方法时属于实例,在改方法上的锁是实例级别的锁,并不能保护静态变量counter,对于这个问题,一方面可以通过将getCounter()改成静态的方法然后再加锁,或者是直接对静态变量加锁来解决。

    3. 锁的粗粒度与使用场景不一致

    锁是有不同的粗粒度的,锁的粗粒度过大必然会影响到性能,锁的粗粒度过小又有可能保护不到想要保护的资源,在使用锁的时候也要考虑要选用什么样的颗粒度的锁。

    @Slf4j
    public class LockLevel {
    
        private List<Integer> data = new ArrayList<>();
    
        // 模拟一个慢方法
        private void slow() {
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
    
            }
        }
    
        public  int wrong() {
            long begin = System.currentTimeMillis();
            IntStream.rangeClosed(1, 1000).parallel().forEach( i -> {
                synchronized (this) {
                    slow();
                    data.add(i);
                }
            });
            log.info("took:{}", System.currentTimeMillis() - begin);
            return data.size();
        }
    
        public int right() {
            long begin = System.currentTimeMillis();
            IntStream.rangeClosed(1, 1000).parallel().forEach(i -> {
                slow();
                synchronized (data) {
                    data.add(i);
                }
            });
            log.info("took:{}", System.currentTimeMillis() - begin);
            return data.size();
        }
    
        public static void main(String[] args) {
            LockLevel lockLevel = new LockLevel();
            System.out.println("wrong" + lockLevel.wrong());
            System.out.println("right" + lockLevel.right());
        }
    
    }
    

    执行结果

    20:14:15.169 [main] INFO com.cy.lock.LockLevel - took:12120
    wrong1000
    20:14:18.140 [main] INFO com.cy.lock.LockLevel - took:2960
    right2000
    
    Process finished with exit code 0
    

    由结果可以看出,采用不同粗粒度锁的方法执行效率有着非常大的差异,其区别就是一个把慢方法放到了加锁的范围内,一个放在了锁的范围外。因此选择锁的时候也要考虑具体的使用场景,选择合适的锁来尽量减少对效率的影响。

    4. 锁的使用不当导致的死锁

    • 模拟一个可以导致死锁的场景
    @RestController
    @RequestMapping("deadlock")
    @Slf4j
    public class DeadLockController {
    
        static class Item {
            final String name;
            int remaining = 1000;
    
            public Item(String name) {
                this.name = name;
            }
    
            ReentrantLock lock = new ReentrantLock();
    
            public String getName() {
                return name;
            }
        }
    
        private ConcurrentHashMap<String, Item> items = new ConcurrentHashMap<>();
    
        public DeadLockController() {
            IntStream.range(0, 10).forEach(i -> items.put("item" + i, new Item("item" + i)));
        }
    
        private List<Item> createCart() {
            return IntStream.rangeClosed(1, 3)
                    .mapToObj(i -> "item" + ThreadLocalRandom.current().nextInt(items.size()))
                    .map(name -> items.get(name)).collect(Collectors.toList());
        }
    
    
        private boolean createOrder(List<Item> order) {
            List<ReentrantLock> locks = new ArrayList<>();
            for (Item item : order) {
    
                try {
                    if (item.lock.tryLock(10, TimeUnit.SECONDS)) {
                        locks.add(item.lock);
                    } else {
                        locks.forEach(ReentrantLock::unlock);
                        return false;
                    }
                } catch (InterruptedException e) {
    
                }
            }
    
            try {
                order.forEach(item -> item.remaining --);
            } finally {
                locks.forEach(ReentrantLock::unlock);
            }
    
            return true;
    
        }
    
        @GetMapping("/wrong")
        public long wrong() {
            long begin = System.currentTimeMillis();
            long success = IntStream.rangeClosed(1, 1000).parallel()
                    .mapToObj(i -> {
                        List<Item> cart = createCart().stream()
                                .sorted(Comparator.comparing(Item::getName))
                                .collect(Collectors.toList());
                        return createOrder(cart);
                    })
                    .filter(result -> result)
                    .count();
            log.info("success:{} totalRemaining:{} took:{}ms items:{}",
                    success,
                    items.entrySet().stream().map(item -> item.getValue().remaining).reduce(0, Integer::sum),
                    System.currentTimeMillis() - begin, items);
            return success;
        }
    
    
    }
    
    
  • 相关阅读:
    《三极管应用分析精粹》终审完成,很快就要印刷了!
    关于SPAPI注册,SP-API注册,SPAPI申请,SP-API申请,开发人员资料注册,amazon亚马逊开发人员资料申请注册,amazon亚马逊销售合作伙伴 API申请注册,SP-API申请注册,amazon亚马逊Selling Partner API申请注册详细指导
    日照的那片海
    Cesium地下模式应用示例
    nginx-1.12.2解决跨域问题nginx.conf设置参考记录
    产品功能被像素级抄袭了。我们拿什么来保护原创的产品设计?
    网线的特征阻抗是多少?协议转换器上连接2m线,其非平衡阻抗是多少欧姆?
    ArrayList、LinkedList、HashSet、HashMap、Iterator
    java基础(枚举、包)
    微服务架构、ELK、ETL
  • 原文地址:https://www.cnblogs.com/cy1995/p/13276002.html
Copyright © 2011-2022 走看看