Too fragrant! Finally, Ali Daniel explained Java multithreading performance optimization in 15 minutes

Posted by davitz38 on Wed, 09 Feb 2022 06:42:43 +0100

hello everyone! I am an old ape. I love technology. I have been in the Java industry for 7 years and am on the way to learn and share every day!

text

We use multithreading just to improve performance, but if multithreading is not used properly, not only the performance improvement is not obvious, but also the resource consumption will be greater. Here are some points that may cause multithreading performance problems:

  • deadlock
  • Excessive serialization
  • Excessive lock competition
  • Switch context
  • Memory synchronization

The above performance hazards are analyzed below

deadlock

As for deadlock, we know its causes and hazards when we learn the operating system. We won't elaborate on the principle here. We can review the causes of deadlock from the following codes and diagrams:

public class LeftRightDeadlock {  
    private final Object left = new Object();  
    private final Object right = new Object();  
    public void leftRight() {  
        synchronized (left) {  
            synchronized (right) {  
                doSomething();  
            }  
        }  
    }  
    public void rightLeft() {  
        synchronized (right) {  
            synchronized (left) {  
                doSomethingElse();  
            }  
        }  
    }  
}  

Methods to prevent and handle Deadlock:

1) Try not to compete with other locks before releasing them

Generally, it can be realized by refining the synchronization method. Take the lock only where the shared resources really need to be protected and release the lock as soon as possible, which can effectively reduce the situation of calling other synchronization methods in the synchronization method

2) Sequential request lock resource

If it is really impossible to avoid nested requests for lock resources, you need to formulate a strategy for requesting lock resources. First plan the locks, and then each thread requests them in one order. Do not appear in different orders in the above example, which will lead to potential deadlock problems

3) Try timing lock

Java 5 provides a more flexible lock tool, which can explicitly request and release locks. Then, you can set a timeout when requesting the lock. If you don't get the lock after this time, you won't continue to block but give up the task. The example code is as follows:

public boolean trySendOnSharedLine(String message,  
                                   long timeout, TimeUnit unit)  
                                   throws InterruptedException {  
    long nanosToLock = unit.toNanos(timeout)  
                     - estimatedNanosToSend(message);  
    if (!lock.tryLock(nanosToLock, NANOSECONDS))  
        return false;  
    try {  
        return sendOnSharedLine(message);  
    } finally {  
        lock.unlock();  
    }  
}  

This can effectively break the deadlock condition.

4) Check deadlock

The JVM uses thread dump to identify deadlocks. You can send the signal of thread dump to the JVM through the command of the operating system, so that you can query which thread deadlocks.

Excessive serialization

Using multithreading is actually to do things in parallel, but these things must work serially due to some dependencies, resulting in the serialization of many links, which actually limits the scalability of the system. Even with CPU and threads, the performance does not increase linearly. There is an Amdahl theorem to illustrate this problem:

Among them, F is the serialization ratio and N is the number of processors. It can be seen from the above that only by reducing serialization as much as possible can we maximize the scalability. The key to reducing serialization is to reduce lock competition. When many parallel tasks are linked to lock acquisition, it is the performance of serialization

Excessive lock competition

The harm of excessive lock competition is self-evident. Let's see what ways to reduce lock competition

1) Narrow the scope of the lock

This point has also been mentioned earlier. Try to narrow the scope of lock protection and fast in and fast out. Therefore, try not to use the synchronized keyword directly on the method, but only where thread safety protection is really needed

2) Reduce lock granularity

Java 5 provides explicit locking, which makes it more flexible to protect shared variables. The synchronized keyword (used in methods) takes the whole object as a lock by default. In fact, it is not necessary to use such a large lock in many cases, which will lead to the serial execution of all synchronized in this class. The shared variables that really need to be protected can be used as locks, or more sophisticated strategies can be used. The purpose is to serial when serial is really needed. For example:

public class StripedMap {  
    // Synchronization policy: buckets[n] guarded by locks[n%N_LOCKS]  
    private static final int N_LOCKS = 16;  
    private final Node[] buckets;  
    private final Object[] locks;  
    private static class Node { ... }  
    public StripedMap(int numBuckets) {  
        buckets = new Node[numBuckets];  
        locks = new Object[N_LOCKS];  
        for (int i = 0; i < N_LOCKS; i++)  
            locks[i] = new Object();  
    }  
    private final int hash(Object key) {  
        return Math.abs(key.hashCode() % buckets.length);  
    }  
    public Object get(Object key) {  
        int hash = hash(key);  
        synchronized (locks[hash % N_LOCKS]) {  
            for (Node m = buckets[hash]; m != null; m = m.next)  
                if (m.key.equals(key))  
                    return m.value;  
        }  
        return null;  
    }  
    public void clear() {  
        for (int i = 0; i < buckets.length; i++) {  
            synchronized (locks[i % N_LOCKS]) {  
                buckets[i] = null;  
            }  
        }  
    }  
    ...  
}  

In the above example, the hash value corresponding to the accessed value is used as a lock through the hash algorithm. In this way, only the objects with the same hash value need to be accessed and serialized, rather than any operation on any object like HashTable.

3) Reduce reliance on shared resources

Shared resources are the source of competitive locks. In multi-threaded development, we should try to reduce the dependence on shared resources. For example, the technology of object pool should be considered carefully. The new JVM has made sufficient optimization for new objects, and the performance is very good. If the object pool is used, not only the performance can not be improved, but the concurrency of threads will be reduced due to lock competition.

4) Use exclusive locks to replace read and write locks

Java 5 provides a ReadWriteLock to realize the characteristics of read-write concurrency, read-write serial and write write serial. This method further improves the concurrency, because some scenarios are mostly read operations, so there is no need to work serially. For the specific use of ReadWriteLock, please refer to the following examples:

public class ReadWriteMap<K,V> {  
    private final Map<K,V> map;  
    private final ReadWriteLock lock = new ReentrantReadWriteLock();  
    private final Lock r = lock.readLock();  
    private final Lock w = lock.writeLock();  
    public ReadWriteMap(Map<K,V> map) {  
        this.map = map;  
    }  
    public V put(K key, V value) {  
        w.lock();  
        try {  
            return map.put(key, value);  
        } finally {  
            w.unlock();  
        }  
    }  
    // Do the same for remove(), putAll(), clear()  
    public V get(Object key) {  
        r.lock();  
        try {  
            return map.get(key);  
        } finally {  
            r.unlock();  
        }  
    }  
    // Do the same for other read-only Map methods  
}  

Switch context

When there are many threads, the performance consumption of operating system switching thread context cannot be ignored

Memory synchronization

When synchronized, volatile or Lock is used, more memory synchronization will be caused to ensure visibility, which makes it impossible to enjoy the performance optimization brought by JMM structure.

Many people worry that learning is easy to forget. Here is a way to teach you, that is, repeat learning.

last

Thank you very much for seeing here. It's not easy to be original. If you think this practical operation method is useful to you, please help one click three times, pay attention and comment are all support (don't go whoring for nothing)! If you have any questions, please leave a message in the comment area!!!!

I will regularly share 6-7 original articles on dry goods such as operating system, computer network, Java, distributed and database every week. See you in the next article!


Topics: Java Multithreading Programmer Concurrent Programming