This article is for the blogger to study notes, the content comes from the Internet, if there is infringement, please contact delete.

Personal note: www.dbses.cn/technotes

Pit 5: The lock was applied on different levels and the result was not as expected

  • Case scenario
class Data {
    @Getter
    private static int counter = 0;

    public static int reset(a) {
        counter = 0;
        return counter;
    }
  
    public synchronized void wrong(a) { counter++; }}Copy the code

The test code is as follows:

@GetMapping("wrong")
public int wrong(@RequestParam(value = "count", defaultValue = "1000000") int count) {
    Data.reset();
    IntStream.rangeClosed(1, count).parallel().forEach(i -> new Data().wrong());
    return Data.getCounter();
}
Copy the code

The page outputs 639242 instead of the expected output of 1 million.

  • Cause analysis,

Static fields belong to classes, which are protected only by class-level locks. Instead of static fields belonging to class instances, instance-level locks are protected.

  • The solution

Lock all the class objects.

class Data {
    @Getter
    private static int counter = 0;
    private static Object locker = new Object();

    public static int reset(a) {
        counter = 0;
        return counter;
    }
  
    public void right(a) {
        synchronized(locker) { counter++; }}}Copy the code

Stomp pit 6: Excessive lock granularity causes performance problems

  • Case scenario

In the business code, there is an ArrayList that needs to be secured because it will be operated on by multiple threads, and there is a time-consuming operation (the slow method in the code) that has no thread-safety implications. The specific code is as follows:

private List<Integer> data = new ArrayList<>();

private void slow(a) {
    try {
        TimeUnit.MILLISECONDS.sleep(10);
    } catch (InterruptedException e) {
    }
}

@GetMapping("wrong")
public int wrong(a) {
    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();
}
Copy the code

This locking performance is very low.

  • Cause analysis,

Even if we do have some shared resources that need to be secured, we want to keep the granularity of locking as low as possible and lock only the necessary code blocks or even the resources themselves that need to be secured.

  • The solution
@GetMapping("right")
public int right(a) {
    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();
}
Copy the code

The comparison takes 11 seconds and 1.4 seconds respectively for the same 1000 service operations.

Pit 7: Deadlocks appear when placing orders, leading to high failure rate of placing orders

  • Case scenario

The operation of placing an order needs to lock the inventory of multiple goods in the order. After obtaining the locks of all goods, the operation of placing an order and reducing the inventory is carried out. After completing all the operations, all the locks are released. After the code went online, it was found that the probability of order failure was very high, and users had to place orders again after failure, which greatly affected user experience and sales volume.

Product Definition:

@Data
@RequiredArgsConstructor
static class Item {
    final String name;
    int remaining = 1000;
    @ToString.Exclude
    ReentrantLock lock = new ReentrantLock();
}
Copy the code

Add items to cart:

private ConcurrentHashMap<String, Item> items = new ConcurrentHashMap<>();

public DeadLockController(a) {
    IntStream.range(0.10).forEach(i -> items.put("item" + i, new Item("item" + i)));
}

private List<Item> createCart(a) {
    return IntStream.rangeClosed(1.3)
            .mapToObj(i -> "item" + ThreadLocalRandom.current().nextInt(items.size()))
            .map(name -> items.get(name)).collect(Collectors.toList());
}
Copy the code

Place the order:

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;
}
Copy the code

Test code:

@GetMapping("wrong")
public long wrong(a) {
    long begin = System.currentTimeMillis();
    long success = IntStream.rangeClosed(1.100).parallel()
            .mapToObj(i -> {
                List<Item> cart = createCart();
                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;
}
Copy the code

The following logs are displayed:

It can be seen that 65 times of 100 orders were successful, with a total of 10,000 pieces of 10 commodities and 9805 pieces of inventory. 195 pieces were consumed in line with expectations (65 orders were successful, each order contained three items), and the total time was 50 seconds.

  • Cause analysis,

* * * * * * * * * * * * * * * * * * * * *

Check the thread stack captured, you can see the following log in the middle of the page:

Why is there a deadlock problem?

Let’s say the items in one cart are Item1 and Item2, and the items in the other cart are Item2 and Item1. One thread obtains the lock of Item1 and another thread obtains the lock of Item2. The threads then acquire the lock on Item2 and Item1, respectively. The lock has already been acquired by the other thread, and they have to wait for each other until 10 seconds have expired.

  • The solution

Order the items in the cart so that all threads must first acquire the lock for Item1 and then acquire the lock for Item2, and there will be no problem.

@GetMapping("right")
public long right(a) {
    long begin = System.currentTimeMillis();
    long success = IntStream.rangeClosed(1.100).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;
}
Copy the code

If you test the Right method, no matter how many times you execute it, you get 100 successful orders, and the performance is quite high, reaching over 3000 TPS:

Deadlocks are avoided here by avoiding circular waiting.

Knowledge of WRK pressure measuring tools