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