preface

Why did Lao Wang scream at night? How could a few lines of code cause a server to explode? Why is thread safety still a problem? Let’s join us today on Access to IT.

The body of the

CurrentHashMap appears in the background

The context for the emergence of ConcurrentHashMap is the HashMap.

Lao Wang is a company’s helpless pain Java development, in the Internet industry, business always iteration very fast. In terms of code, v1.0 modules are single-threaded, so using HashMap is a good choice. However, in the v1.5 version, for the sake of performance, Lao Wang thought it would be more efficient to change this code to multi-threading, so he said to change, and then happily released online.

Until one night, you get an online alert and the server is 100% CPU occupied. At this time, I woke up to a thorough investigation (Baidu and Google), and it was found that HashMap would cause a closed loop of linked list when rehash was performed in a concurrent environment, so the CPU usage was 100% when get() was performed. Well, it turns out that HashMap is not a thread-safe class, which can be problematic in current business scenarios. Hashtable is a thread safe class, so I can use this class to replace it with a commit/push class.

But the good days do not last long, the operation of the colleagues and come to the door, Lao Wang ah, XX function how slow so much ah? At this time Lao Wang wondered, I did not change the code ah? We replaced a Hashtable last time. Then it was another search (Baidu, Google). Sure enough, it was thread safe because synchronized was added to the method, which led to all our operations being serialized. No wonder it was so slow.

At this time, he found ConcurrentHashMap. In order to avoid falling into the trap again, he also learned the implementation principle in advance. Originally, this class uses Segment Segment lock, and each Segment has its own lock. This reduces the scope of conflict and improves efficiency. After investigation, it was found to be really good, so he was relieved to replace Hashtable. From then on, the operation never came to ridicule, and Lao Wang lived a happy life again.

After a period of intense business development, the project has been moved to V2.0. The previous code related to ConcurrentHashMap has been changed beyond recognition, and the logic has become much more complex. However, the project has been successfully launched on time. After running the project for a period of time, the thread safety problem unexpectedly appears again. The root cause is ConcurrentHashMap. Lao Wang Yi fell into a meditation.

Why did it go wrong?

Instead of a complicated example, let’s use a multithread to simultaneously fetch the values in the map and increment them by 1, and see what the output number looks like

public class CHMDemo {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String,Integer>();
        map.put("key".1);
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run(a) {
                    int key = map.get("key") + 1; //step 1
                    map.put("key", key);//step 2}}); } Thread.sleep(3000); // The simulation waits for execution to end
        System.out.println("-- -- -- -- -- -" + map.get("key") + "-- -- -- -- -- -"); executorService.shutdown(); }}Copy the code

At this point, let’s look at the output of multiple executions

-- -- -- -- -- -- 790 -- -- -- -- -- -- -- -- -- -- -- -- 825 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 875Copy the code

Looking at the output, you can see that this code, which uses ConcurrentHashMap, causes thread-safety issues. Let’s analyze why this happens. In step1 and step2, only the ConcurrentHashMap method is called. Both are atomic operations and thread-safe. Get (“key”) to get the value of the key. As soon as the value of the key is read out, thread B also comes in. This results in thread A and thread B getting the same key. Not just in

ConcurrentHashMap. This is also the case with other thread-safe containers such as vectors, so use these containers with caution.

How to solve it?

1. Synchronized

synchronized(this) {//step1
    //step2
}

Copy the code

But with this approach, we have to think about efficiency, does it have a big impact on the current business?

2. Use atomic classes

public class CHMDemo {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<String,AtomicInteger>();
        AtomicInteger integer = new AtomicInteger(1);
        map.put("key", integer);
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run(a) {
                    map.get("key").incrementAndGet(); }}); } Thread.sleep(3000); // The simulation waits for execution to end
        System.out.println("-- -- -- -- -- -" + map.get("key") + "-- -- -- -- -- -"); executorService.shutdown(); }}Copy the code
-- -- -- -- -- -- -- -- -- -- -- - 1001Copy the code

The output is correct and much more efficient than the first solution.

conclusion

Life is full of pitfalls, and so is writing code. Think and pay attention.


Recommended reading

Learning Synchronous/Asynchronous/Blocking/Non-Blocking in English, Java Exception Handling Best Practices and Trap Prevention, JVM Explosion Posture and Self-Rescue Methods, Supervisor of Freeing Programmer hands

Follow the late Night programmer to share the driest stuff