使用锁的时候需要注意的坑

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;
    }


}

posted @ 2020-07-09 20:28  三弦音无  阅读(181)  评论(0编辑  收藏  举报