In-depth analysis of the differences between hash tables, hash maps and concurrent hash maps, as well as the causes and solutions of deadlocks

deadlock

Deadlock is a common problem in multi-threaded programming. Deadlock occurs when two or more threads are waiting for resources held by each other and cannot continue execution. In this case, the program will fall into an unrecoverable state, causing the program to stall or crash. The following are common causes and solutions for deadlocks.

Deadlock conditions

  1. Mutually exclusive access to resources: Multiple threads compete with each other to access resources. If a resource is held by one thread, other threads cannot obtain the resource.
  2. Non-preemptible: Resource requesters cannot forcibly seize resources from resource occupiers, and resources can only be released actively by resource occupiers.
  3. Circular waiting: Multiple threads form a circular resource dependency relationship, and each thread is waiting for the next thread to release resources.
  4. Occupying and waiting: While holding a resource, a thread requests other resources, causing other threads to be unable to continue execution.
    When the above four conditions are true, a deadlock occurs.

For example, the "Philosopher Dining Problem" has a group of philosophers eating around a table, with a chopstick placed between every two philosophers. The philosophers only do two things: thinking about life or eating noodles. When you think about life, you put down your chopsticks. When eating noodles, you will pick up the chopsticks on the left and right sides (pick up the left side first, then the right side). When philosophers find that they cannot pick up the chopsticks, they will be blocked and waiting to think about life. If five philosophers pick up the left chopsticks at the same time, and then pick up the right chopsticks, they will find that the chopsticks have been occupied, and they will be blocked and wait, thinking about life. If the philosophers wait for each other, a "deadlock" will be formed. .

solution

How to solve the deadlock, that is, break the four necessary conditions for the formation of the deadlock.

  • Destroying mutual exclusion conditions: For some non-essential resources, you can change them to shared resources, and multiple threads can access them at the same time, thereby avoiding mutual exclusion.
  • Destroy occupation and wait: When a thread needs multiple resources, obtain the required resources at once instead of obtaining them one by one, or adopt a resource pre-allocation strategy.
  • Destroy cyclic waiting: Avoid cyclic waiting by numbering resources or applying for resources in a fixed order.

One of the most easily damaged is "loop waiting"

One of the most commonly used deadlock prevention techniques to break cyclic waits is lock ordering. Assuming that there are N threads trying to acquire M locks, the M locks can be numbered. When N threads try to acquire the lock, they all acquire the lock in a fixed order from small to large numbers. This avoids loop waiting.

The following demonstrates the code that will generate loop waiting and the code that will not generate loop waiting.
Loop waits may occur:

  public static void main(String[] args) {
    
    
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(){
    
    
            @Override
            public void run() {
    
    
                //获取锁
                synchronized (lock1){
    
    
                    synchronized (lock2){
    
    
                       
                    }
                }
            }
        };

        Thread t2 = new Thread(){
    
    
            @Override
            public void run() {
    
    
                synchronized (lock2){
    
    
                    synchronized (lock1){
    
    
                     
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }

Code that does not create a loop wait:

  public static void main(String[] args) {
    
    
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(){
    
    
            @Override
            public void run() {
    
    
                //获取锁
                synchronized (lock1){
    
    
                    synchronized (lock2){
    
    

                    }
                }
            }
        };

        Thread t2 = new Thread(){
    
    
            @Override
            public void run() {
    
    
                synchronized (lock1){
    
    
                    synchronized (lock2){
    
    

                    }
                }
            }
        };
        t1.start();
        t2.start();
    }

Agree on the order, first acquire the lock1 lock and then the lock2 lockInsert image description here

HashTable

HashTable just adds the synchronized keyword to the key method.
Insert image description here
This is equivalent to directly locking the hashtable object.

  • Multiple threads accessing the same hashtable will cause lock conflicts
  • Due to the existence of synchronization locks, the performance of hash tables is relatively poor. In high concurrency situations, multi-thread competition may lead to performance degradation.
  • Hash tables do not allow null keys and values.

ConcurrentHashMap

  • The head node of each linked list is used as a lock object, which reduces the probability of lock conflicts.
  • The read operation is not locked, but the write operation is locked and still uses synchronized.
  • Take full advantage of CAS features
  • Optimized the expansion method
    1. Divide it into zeros. When it is found that the capacity needs to be expanded, a new array will be created and part of the data will be moved.
    2. When inserting a new element, the element will be directly inserted into the new table and part of the data will be transferred.
    3. When deleting an element, both old and new tables will be searched. Which table the element is in will be deleted on that table.
    4. The old table will not be deleted until the elements of the old table are moved.

A lock conflict occurs only when two threads access data on the same hash bucket.

HashMap

  • Thread-unsafe: Hash maps are not thread-safe and are not suitable for multi-threaded environments. When used in a multi-threaded environment, data inconsistency may occur.
  • Performance: The performance of hash mapping is better. Since there is no overhead of synchronization locks, insertion, search and deletion operations can be performed faster.

Guess you like

Origin blog.csdn.net/st200112266/article/details/133148079