JUC high concurrency programming II

Keywords: Java Back-end Concurrent Programming

5. Collection thread safety

  1. Thread unsafe demo for collection

    1. ArrayList
    package com.codetip.codejuc.juc.conllections;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;
    
    public class ThreadArrayList {
    
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i <= 100; i++) {
                new Thread(() -> {
                    // Add content to thread
                    list.add(UUID.randomUUID().toString().substring(0, 8));
                    // Take the content out of it
                    System.out.println(list); // When the collection is unsafe, a thread is still putting in the content, resulting in concurrent modification
                }, String.valueOf(i)).start();
            }
        }
    }
    
    

    The operation results are as follows:

     

    1. Solution:

      1. Use vector (infrequently - inefficient)
      public class ThreadArrayListOnVector {
          public static void main(String[] args) {
              List<String> list = new Vector<>(); // Using vector
              for (int i = 0; i <= 100; i++) {
                  new Thread(() -> {
                      // Add content to thread
                      list.add(UUID.randomUUID().toString().substring(0, 8));
                      // Take the content out of it
                      System.out.println(list);
                  }, String.valueOf(i)).start();
              }
          }
      }
      
      1. Using Collections
      public class ThreadArrayListOnCollections {
          public static void main(String[] args) {
              // Tool classes through Collections Toolkit
              List<String> list = Collections.synchronizedList(new ArrayList<>());
              for (int i = 0; i <= 100; i++) {
                  new Thread(() -> {
                      // Add content to thread
                      list.add(UUID.randomUUID().toString().substring(0, 8));
                      // Take the content out of it
                      System.out.println(list);
                  }, String.valueOf(i)).start();
              }
          }
      }
      
      

      1. Use copy on write technology (CopyOnWriteArrayList in JUC)

        1. Overview: multiple threads read the same content when reading, copy the same content before writing, and overwrite or merge the content before writing. The code is as follows:
          public static void main(String[] args) {
              // Using write time replication technology in Juc
              List<String> list = new CopyOnWriteArrayList<>();
              for (int i = 0; i <= 100; i++) {
                  new Thread(() -> {
                      // Add content to thread
                      list.add(UUID.randomUUID().toString().substring(0, 8));
                      // Take the content out of it
                      System.out.println(list);
                  }, String.valueOf(i)).start();
              }
          }
      

      The source code is as follows:

      // CopyOnWriteArrayList add method source code 
      public boolean add(E e) {
              final ReentrantLock lock = this.lock; 
              lock.lock();// Lock
              try {
                  Object[] elements = getArray();// Gets the array of the current collection
                  int len = elements.length;
                  Object[] newElements = Arrays.copyOf(elements, len + 1); // Make a new copy
                  newElements[len] = e; // Write new content
                  setArray(newElements); // (reassignment) content before merging
                  return true;
              } finally {
                  lock.unlock(); // Unlock
              }
          }
      
  2. HashSet

    1. Thread unsafe demo

      // Demonstrate thread insecurity in HashSet  
      public static void main(String[] args) {
              Set<String> list = new HashSet<>();
              for (int i = 0; i <= 100; i++) {
                  new Thread(() -> {
                      // Add content to thread
                      list.add(UUID.randomUUID().toString().substring(0, 8));
                      // Take the content out of it
                      System.out.println(list);
                  }, String.valueOf(i)).start();
              }
          }
      

       

    2. Solution

      1. Use CopyOnWriteArraySet in Juc to solve the problem
      public static void main(String[] args) {
              Set<String> list = new CopyOnWriteArraySet<>();
              for (int i = 0; i <= 100; i++) {
                  new Thread(() -> {
                      // Add content to thread
                      list.add(UUID.randomUUID().toString().substring(0, 8));
                      // Take the content out of it
                      System.out.println(list);
                  }, String.valueOf(i)).start();
              }
          }
      
      • The bottom layer of HashSet is actually a HashMap, and the value of HashMap is the value of HashSet. The Key of HashMap is non repeatable and unordered.

        The source code is as follows:

        private transient HashMap<E,Object> map;
        public boolean add(E e) {
                return map.put(e, PRESENT)==null;
        }
        
  3. HashMap

    1. Thread unsafe demo

          // HashMap thread unsafe demo
          public static void main(String[] args) {
              Map<String, String> map = new HashMap<>();
              for (int i = 0; i <= 100; i++) {
                  String key = String.valueOf(i);
                  new Thread(() -> {
                      // Add content to thread
                      map.put(key, UUID.randomUUID().toString().substring(0, 8));
                      // Take the content out of it
                      System.out.println(map);
                  }, String.valueOf(i)).start();
              }
          }
      

       

    2. Solution: use the ConcurrentHashMap in the Juc package

      public static void main(String[] args) {
              Map<String, String> map = new ConcurrentHashMap<>(); // ConcurrentHashMap in JUC package
              for (int i = 0; i <= 100; i++) {
                  String key = String.valueOf(i);
                  new Thread(() -> {
                      // Add content to thread
                      map.put(key, UUID.randomUUID().toString().substring(0, 8));
                      // Take the content out of it
                      System.out.println(map);
                  }, String.valueOf(i)).start();
              }
          }
      

6. Multithreaded lock

  1. Demonstration of eight locks

    package com.codetip.codejuc.juc;
    
    import java.util.concurrent.TimeUnit;
    
    class Phone {
        public synchronized void sendSms() throws InterruptedException {
    
            // 2. Stop for four seconds and print SMS or email first
            // Stay for four seconds
            TimeUnit.SECONDS.sleep(4);
            System.out.println("Send SMS-----send_SMS");
        }
    
        public synchronized void sendEmail() {
            System.out.println("Send mail-----send_Email");
        }
    
        public static synchronized void sendSmsS() throws InterruptedException {
    
            // 2. Stop for four seconds and print SMS or email first
            // Stay for four seconds
            TimeUnit.SECONDS.sleep(4);
            System.out.println("Send SMS-----send_SMS");
        }
    
        public static synchronized void sendEmailS() {
            System.out.println("Send mail-----send_Email");
        }
    
    
    
        public void getHello() {
            System.out.println("-------getHello");
        }
    
    }
    
    
    /**
     * 8 lock
     * 1. A mobile phone, standard access, print SMS or email first, comment out the dwell time first, and then open it all
     * Send SMS ----- send_SMS
     * Send mail ----- send_Email
     *
     * 2.A mobile phone, stay for four seconds, print SMS or email first
     * Send SMS ----- send_SMS
     * Send mail ----- send_Email
     * Cause analysis: 1 and 2. Execution sequence: the synchronized method on the first sms execution method has locked the current object, and Email is waiting to obtain the lock
     * synchronized The lock is the current object
     *
     * 3.Add a common Hello method. Print SMS or hello first
     * -------getHello
     * Send SMS ----- send_SMS
     *
     * Reason: the common method does not lock, which has nothing to do with the lock, so it is implemented directly
     *
     * 4.Now there are two mobile phones. Print SMS or email first
     * Send mail ----- send_Email
     * Send SMS ----- send_SMS (wait four seconds)
     * Cause analysis: there are two objects. The synchronized lock is only the current object, and the same lock is not used,
     *
     * 5.Two static synchronization methods: 1 mobile phone, print SMS or email first
     * Send SMS ----- send_SMS
     * Send mail ----- send_Email
     *
     * 6.Two static synchronization methods, two mobile phones, print SMS or email first
     * Send SMS ----- send_SMS
     * Send mail ----- send_Email
     *
     * Cause analysis 5 and 6 static synchronized locked ranges send changes, not the current object this, but the Class object of the current Class (bytecode object of the Class)
     *
     * 7. A static synchronization method, a common method and a mobile phone. Print SMS or email first
     * Send mail ----- send_Email
     * Send SMS ----- send_SMS
     *
     * 8.A static synchronization method, a common method, 2 mobile phones, print SMS or email first
     * Send mail ----- send_Email
     * Send SMS ----- send_SMS (stay for four seconds)
     *
     * Cause analysis: 7 and 8 still have different scope of lock. static synchronized locks the Class object of the Class, and the current object this without static locking
     *
     */
    
    public class EightLock {
    
        public static void main(String[] args) throws InterruptedException {
            Phone phone1 = new Phone();
            Phone phone2 = new Phone();
            new Thread(() -> {
                try {
                    // 1. Standard access, print SMS or email first
                    // 2. Stop for four seconds and print SMS or email first
                    // 3. Add a common Hello method, whether to print SMS or hello first
                    //phone1.sendSms();
    
                    // 5. Two static synchronization methods: one mobile phone, print SMS or email first
                    // 6. Two static synchronization methods, two mobile phones, print SMS or email first
                    // 7. A static synchronization method, a common method and a mobile phone. Print SMS or email first
                    // 8. One static synchronization method, one common method and two mobile phones. Print SMS or email first
                    phone1.sendSmsS();
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    // 1. Standard access, print SMS or email first
                    // 2. Stop for four seconds and print SMS or email first
                   // phone1.sendEmail();
    
    
                    // 3. Add a common Hello method, whether to print SMS or hello first
                    // phone1.getHello();
    
                    // 4. Now there are two mobile phones. Print SMS or email first
                    // phone2.sendEmail();
    
                    // 5. Two static synchronization methods: one mobile phone, print SMS or email first
                    // phone1.sendEmailS();
    
    
                    // 6. Two static synchronization methods, two mobile phones, print SMS or email first
                    // phone2.sendEmailS();
    
                    // 7. A static synchronization method, a common method and a mobile phone. Print SMS or email first
                    // phone1.sendEmail();
    
                    // 8. One static synchronization method, one common method and two mobile phones. Print SMS or email first
                    phone2.sendEmail();
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }
    
    

    Summary: synchronized is the basis of synchronization: every object in Java can be used as a lock, which is shown in the following three cases

    1. For the normal method: the current instance object of the lock (commonly known as This)
    2. For static synchronization methods: the lock is the Class object of the current Class
    3. For the synchronization method block, the locked object is the configured object in the synchronized bracket
  2. Fair lock and unfair lock

    1. The specific codes are as follows

      // The default is a non fair lock
      private final Lock lock = new ReentrantLock();
      // The source code is as follows
        public ReentrantLock() {
              sync = new NonfairSync();
        }
      
      // How to create a class using a fair lock, pass in true in the construction parameter to indicate the use of a fair lock
       public ReentrantLock(boolean fair) {
              sync = fair ? new FairSync() : new NonfairSync();
       }
      // The source code is as follows
      
         static final class FairSync extends Sync {
              private static final long serialVersionUID = -3000897897090466540L;
      
              final void lock() {
                  acquire(1); //  Attempt to acquire lock
              }
      
              /**
               * Fair version of tryAcquire.  Don't grant access unless
               * recursive call or no waiters or is first.
               */
              protected final boolean tryAcquire(int acquires) {
                  final Thread current = Thread.currentThread();
                  int c = getState();
                  if (c == 0) {
                      //  Judge whether the lock is used
                      if (!hasQueuedPredecessors() &&
                          compareAndSetState(0, acquires)) {
                          setExclusiveOwnerThread(current);
                          return true;
                      }
                  }
                  // Someone lined up with
                  else if (current == getExclusiveOwnerThread()) {
                      int nextc = c + acquires;
                      if (nextc < 0)
                          throw new Error("Maximum lock count exceeded");
                      setState(nextc);
                      return true;
                  }
                  return false;
              }
          }
      
    2. characteristic:

      1. Unfair lock features: other threads are starved to death and are highly efficient. (refer to the example of selling tickets)
      2. Fair lock features: each thread is likely to get more, and the efficiency is slightly lower. (refer to the example of selling tickets)
  3. Reentrant lock (recursive lock)

    1. synchronized implicit reentrant lock (locking and unlocking are done automatically)
    2. Lock: displayed reentrant lock

    Code demonstration: synchronized

    // Case 1 synchronization code block
    Object o = new Object();
            new Thread(() -> {
                synchronized (o) {
                    System.out.println(Thread.currentThread().getName() + "Outer layer");
                    synchronized (o) {
                        System.out.println(Thread.currentThread().getName() + "middle level");
                        synchronized (o) {
                            System.out.println(Thread.currentThread().getName() + "Inner layer");
    
                        }
                    }
                }
            }, "aa").start();
    
    // Case 2 synchronization method
    
    public class ReSyn {
    
        public synchronized void add() {
            add();
        }
    
        // Use the synchronized keyword to demonstrate reentrant locks
        public static void main(String[] args) {
                new ReLock().add();
        }
    
    }
    
    // Case 2: memory overflow occurs during operation. Reason: synchronized is a reentrant lock. The locked add method can also be called (reentrant lock (recursive lock)) because it is the same object
    
    

    Code demo: Lock

     Lock lock = new ReentrantLock();
            // Create thread
            new Thread(() -> {
                try {
    
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "Outer layer");
                    try {
                        lock.lock();
                        System.out.println(Thread.currentThread().getName() + "Inner layer");
                    } finally {
                        lock.unlock();
                    }
                } finally {
                    lock.unlock();
                }
            }, "aa").start();
        }
    

    Demo lock does not close:

    // Use the Lock keyword to Lock without closing
    public static void main(String[] args) {
    Lock lock = new ReentrantLock();
        // Create thread
        new Thread(() -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "Outer layer");
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "Inner layer");
                } finally {
                    // If the lock inside doesn't close
                    // lock.unlock();
                }
            } finally {
                lock.unlock();
            }
        }, "AA").start();
        
        // Create a new thread
        new Thread(() -> {
            try {
    
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "");
    
            } finally {
                lock.unlock();
            }
        }, "BB").start();
      }
    }
    

    The results are as follows: a deadlock occurs. The reason: the above thread is not released, and the following thread cannot obtain the lock.

     

     

  4. Deadlock when two or more threads are executing, they are waiting for each other because they compete for resources. If there is no interference from external forces, they can no longer execute.

    1. Causes of Deadlock:

      1. Insufficient system resources
      2. The thread running sequence is incorrect
      3. Improper allocation of resources
    2. Code demonstration:

      package com.codetip.codejuc.juc.deallock;
      
      import java.util.concurrent.TimeUnit;
      
      public class DealLock {
          static Object a = new Object();
          static Object b = new Object();
      
          public static void main(String[] args) {
              // Hold lock a and wait for lock b
              new Thread(() -> {
                  synchronized (a) {
                      System.out.println(Thread.currentThread().getName() + "Hold lock a,Attempt to acquire lock b");
                      try {
                          TimeUnit.SECONDS.sleep(1);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      synchronized (b) {
                          System.out.println(Thread.currentThread().getName() + "Acquire lock b");
      
                      }
                  }
              }, "aa").start();
              // Hold lock b and wait for lock a
              new Thread(() -> {
                  synchronized (b) {
                      System.out.println(Thread.currentThread().getName() + "Hold lock b,Attempt to acquire lock a");
                      try {
                          TimeUnit.SECONDS.sleep(1);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      synchronized (a) {
                          System.out.println(Thread.currentThread().getName() + "Acquire lock a");
      
                      }
                  }
              }, "bb").start();
          }
      }
      
      

      The operation screenshot is as follows:

       

       

    3. Verify deadlock

      1. Command: use jps (similar to ps -ef in Linux)

      2. Stack trace command in jstack jvm

        Specific demonstration:

        • Command input: jps -l view the process ID of the class to be observed, where the ID is: 3880

           

        • Using jstack 3880, you can see the specific information of deadlock

           

Posted by Nolan on Sun, 28 Nov 2021 14:43:19 -0800