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;
        }
    
    
    }
    
    
  • 相关阅读:
    MDK软件2020年到期和谐方法
    TinyMCE插件CodeSample前后端应用
    KEIL MDK编译后的代码量和RAM使用详解
    pdf密码解除工具
    Keil c中自定义带可变参数的printf函数
    关于"云服务器被检测到对外攻击已阻断该服务器对其它服务器端口的访问"的解决措施
    在 __CC_ARM 编译器环境下,使用$Sub$$ 与 $Super$$ 的“补丁”功能
    thinkphp前台html格式化输出日期
    ELINK编程器典型场景之序列号烧写
    数据在数组中存储的顺序:小端 OR 大端模式 详解
  • 原文地址:https://www.cnblogs.com/cy1995/p/13276002.html
Copyright © 2011-2022 走看看