Summary of JAVA concurrent synchronization and mutual exclusion

Keywords: Java jvm Redis REST

We all know that locking is an effective means to prevent the same resource from being preempted by multiple parties in the concurrent situation. Locking is actually synchronous mutual exclusion (or exclusive). That is to say, no matter how many concurrent requests there are at the same time, only one can handle them, and the rest can either wait in line or give up execution. As for the example of lock implementation, I just sort out and summarize it for reference.

Synchronous mutual exclusion can be divided into:

  1. Synchronization and mutual exclusion between threads

    The following code examples are used to demonstrate the common implementation methods of synchronization and mutual exclusion between threads:

    1. synchronized

          /**
           * Synchronization between threads (synchronized synchronization exclusive lock, open multiple threads, if one thread gets the lock, all other threads block and wait for the queue, when the lock is released, the next thread continues to get the lock, and the other threads still wait)
           */
          private void testSynchronized() {
              ExecutorService executorService = Executors.newFixedThreadPool(2);
              Runnable runnable = () -> {
                  long threadId = Thread.currentThread().getId();
                  for (int i = 0; i <= 100; i++) {
                      synchronized (lockObj) {
                          if (count > 0) {
                              try {
                                  Thread.sleep(200L);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              System.out.printf("threadId:%s,number:%d --count:%d %n", threadId, i, --count);
                          }
                      }
      
                      //If the lock is not applied, there may be a negative number, i.e. concurrency problem
      //                if (count>0){
      //                    try {
      //                        Thread.sleep(200L);
      //                    } catch (InterruptedException e) {
      //                        e.printStackTrace();
      //                    }
      //                    System.out.printf("threadId:%s,number:%d --count:%d %n",threadId,i,--count);
      //                }
                  }
              };
      
              executorService.execute(runnable);
              executorService.execute(runnable);
      
      
              System.out.printf("lasted count:%d", count);
          }
    2. synchronized synchronization mutex + notification waiting mode

      /**
           * Synchronization between threads (synchronized mutual exclusive lock + notification waiting mode, enabling multiple threads, when the lock is obtained, a notification can be sent through the Object.notify() method to notify other waiting locks or wait situations to resume execution. The example shows that production and consumption wait for each other)
           */
          private void testWaitAndNotify() {
              count = 0;
              ExecutorService executorService = Executors.newFixedThreadPool(2);
              Runnable productRunnable = () -> {
                  long threadId = Thread.currentThread().getId();
                  for (int i = 0; i <= 50; i++) {
                      synchronized (lockObj) { //Get lock
                          try {
                              Thread.sleep(200L);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.printf("threadId:%s,number:%d --Post production count:%d %n", threadId, i, ++count);
                          lockObj.notify();//give an announcement
                          try {
                              System.out.printf("threadId:%s,number:%d,Waiting for production%n", threadId, i);
                              if (i == 50) break;
                              lockObj.wait();//Waiting for notification, blocking current thread
                              System.out.printf("threadId:%s,number:%d,Receive notice, prepare for production%n", threadId, i);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
                  count = -1;
                  System.out.printf("threadId:%s,Production is finished.%n", threadId);
              };
      
              Runnable consumeRunnable = () -> {
                  long threadId = Thread.currentThread().getId();
                  for (int i = 0; i <= 200; i++) {
                      synchronized (lockObj) { //Get lock
                          if (count > 0) {
                              try {
                                  Thread.sleep(200L);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                              System.out.printf("threadId:%s,number:%d --After consumption count:%d %n", threadId, i, --count);
                              lockObj.notify(); //give an announcement
                          } else {
                              try {
                                  System.out.printf("threadId:%s,number:%d,Waiting for consumption%n", threadId, i);
                                  if (count == -1) break;
                                  lockObj.wait();//Waiting for notification, blocking current thread
                                  System.out.printf("threadId:%s,number:%d,Receive notice and prepare for consumption%n", threadId, i);
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  }
                  System.out.printf("threadId:%s,It has been consumed.%n", threadId);
      
              };
      
              executorService.execute(consumeRunnable);
              executorService.execute(productRunnable);
      
      
          }
    3. Conditional lock ReentrantLock, Condition

          /**
           * Synchronization between threads (conditional Lock ReentrantLock, Condition, open multiple threads, when lock() obtains the Lock, it can send a signal through the conditional instance method signal of Lock to inform other cases of waiting for Lock or await to resume execution, and the example shows that production and consumption wait for each other)
           */
          private void testLock() {
              final Lock lock = new ReentrantLock();
              final Condition lockCond = lock.newCondition();
      
              count = 0;
              ExecutorService executorService = Executors.newFixedThreadPool(2);
              Runnable productRunnable = () -> {
                  long threadId = Thread.currentThread().getId();
                  lock.lock();//First get the lock.
                  for (int i = 0; i <= 50; i++) {
                      try {
                          Thread.sleep(200L);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.printf("threadId:%s,number:%d --Post production count:%d %n", threadId, i, ++count);
                      lockCond.signal();//Release signal
                      try {
                          System.out.printf("threadId:%s,number:%d,Waiting for production%n", threadId, i);
                          if (i == 50) break;
                          lockCond.await();//Wait for signal, blocking current thread
                          System.out.printf("threadId:%s,number:%d,Receive notice, prepare for production%n", threadId, i);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  lock.unlock();//Release lock
                  count = -1;
                  System.out.printf("threadId:%s,Production is finished.%n", threadId);
              };
      
              Runnable consumeRunnable = () -> {
                  long threadId = Thread.currentThread().getId();
                  lock.lock();//First get the lock.
                  for (int i = 0; i <= 200; i++) {
                      if (count > 0) {
                          try {
                              Thread.sleep(200L);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.printf("threadId:%s,number:%d --After consumption count:%d %n", threadId, i, --count);
                          lockCond.signal();//Release signal
                      } else {
                          try {
                              System.out.printf("threadId:%s,number:%d,Waiting for consumption%n", threadId, i);
                              if (count == -1) break;
                              lockCond.await();//Wait for signal, blocking current thread
                              System.out.printf("threadId:%s,number:%d,Receive notice and prepare for consumption%n", threadId, i);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
      
                  }
                  lock.unlock();
                  System.out.printf("threadId:%s,It has been consumed.%n", threadId);
      
              };
      
              executorService.execute(consumeRunnable);
              executorService.execute(productRunnable);
          }
    4. Future

          /**
           * Synchronization between threads (Future, use Executors.submit to open one or more threads to return to Future, and asynchronous execution in the background of the thread will not block the main thread. When it is necessary to obtain the thread result, that is, Future.get, it will wait for the result,
           * Of course, you can use completabilefuture to achieve the full asynchronous callback processing result without any blocking)
           */
          private void testFuture() {
              //refer:https://www.cnblogs.com/xiaoxi/p/8303574.html
              ExecutorService executorService = Executors.newSingleThreadExecutor();
              Future<Long> task = executorService.submit(() -> {
                  long total = 0;
                  System.out.println("Sub thread for loop start...");
                  for (int i = 0; i <= 100; i++) {
                      try {
                          Thread.sleep(50L);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      total += i;
                  }
                  System.out.printf("Sub thread for loop end,total=%d %n", total);
                  return total;
              });
      
              //The main thread processes other logic, which is executed in parallel with the sub thread
              for (int n = 0; n <= 30; n++) {
                  System.out.printf("Main thread for loop in,n=%d %n", n);
              }
      
              try {
                  long result = task.get();//Wait for the result of the sub thread. If it is not finished, the main thread will be blocked until the sub thread finishes the result
                  System.out.printf("Main thread gets the result of sub thread calculation,total=%d %n", result);
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
              //Use completabilefuture to asynchronously call back to get results without blocking the main thread
              CompletableFuture.supplyAsync(() -> {
                  long total = 0;
                  System.out.println("Sub thread for loop start...");
                  for (int i = 0; i <= 100; i++) {
                      try {
                          Thread.sleep(50L);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      total += i;
                  }
                  System.out.printf("threadId:%s,Sub thread for loop end,total=%d %n", Thread.currentThread().getId(), total);
                  return total;
              }).thenAccept(result -> {
                  //This method will be called back when the execution of the child thread is completed
                  System.out.printf("threadId:%s,Callback gets the result of sub thread calculation,total=%d %n", Thread.currentThread().getId(), result);
              });
      
      
              //The main thread processes other logic, which is executed in parallel with the sub thread
              long threadId = Thread.currentThread().getId();
              for (int n = 0; n <= 30; n++) {
                  System.out.printf("threadId:%s,Main thread for loop2 in,n=%d %n", threadId, n);
              }
      
              System.out.printf("threadId:%s,The main thread has completed execution.%n", threadId);
      
          }
    5. CountDownLatch (similar to CyclicBarrier, supports concurrent execution in batches, waiting in batches, and redesigning the counter)

          /**
           * Synchronization between threads (CountDownLatch, running multiple threads at the same time, main thread will block waiting before CountDownLatch counter count is 0)
           */
          private void testCountDownLatch() {
              ExecutorService executorService = Executors.newFixedThreadPool(3);
              final CountDownLatch latch = new CountDownLatch(3);
              Runnable runnable = () -> {
                  long threadId = Thread.currentThread().getId();
                  for (int i = 0; i <= 100; i++) {
                      try {
                          Thread.sleep(50L);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.printf("threadId:%s,number:%d %n", threadId, i);
                  }
                  System.out.printf("threadId:%s,Processing completed.%n", threadId);
                  latch.countDown();//Deduction counter-1
              };
      
              //Open 3 threads for parallel processing
              for (int i = 1; i <= 3; i++) {
                  executorService.execute(runnable);
              }
      
              long mainThreadId = Thread.currentThread().getId();
              try {
                  System.out.printf("threadId:%s,Main thread waiting...%n", mainThreadId);
                  latch.await();//Wait for all execution to complete, i.e. the counter is 0, blocking the main thread
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              System.out.printf("threadId:%s,The main thread confirms that all sub threads have completed processing,count:%d,Start execution of main thread logic.%n", mainThreadId, latch.getCount());
      
              System.out.printf("threadId:%s,Main thread completed!%n", mainThreadId);
      
      
          }
    6. Semaphore

          /**
           * Synchronization between threads (Semaphore, enable multiple threads, use acquire to obtain one license [you can specify to obtain multiple licenses at a time],
           * If not, wait. If the license has been obtained, the total number of 1 available license is occupied and can be continued. The license shall be released after the completion of execution.)
           */
          private void testSemaphore(){
              Semaphore wcSemaphore = new Semaphore(5,true);
              Runnable runnable =() -> {
                  long threadId = Thread.currentThread().getId();
                  System.out.printf("threadId:%s,Waiting to enter WC,At present there are:%d Number of people waiting in line:%d %n", threadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
                  try {
                      wcSemaphore.acquire();
                      System.out.printf("threadId:%s,Get into WC,At present there are:%d Number of people waiting in line:%d,close %n", threadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
                      Thread.sleep(1000L);
                      System.out.printf("threadId:%s,leave WC,At present there are:%d Number of people waiting in line:%d,Open door %n", threadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
                      wcSemaphore.release();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              };
      
              ExecutorService executorService = Executors.newFixedThreadPool(5);
              for (int n=1;n<=10;n++){
                  executorService.execute(runnable);
              }
      
              long mainThreadId = Thread.currentThread().getId();
              System.out.printf("threadId:%s,Cleaning aunt waiting for cleaning WC,At present there are:%d Number of people waiting in line:%d %n",
                      mainThreadId,wcSemaphore.availablePermits(),wcSemaphore.getQueueLength());
              //Wait if there is still a queue and the remaining space is not fully processed
              while (wcSemaphore.hasQueuedThreads() && wcSemaphore.drainPermits()!=5 && wcSemaphore.availablePermits()!=5){
                  try {
                      Thread.sleep(50L);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
      
              try {
                  wcSemaphore.acquire(5);
                  System.out.printf("threadId:%s,Cleaning aunt starts cleaning WC,Shut;turn off WC The entrance, i.e. all people can't use it anymore, and there are:%d Number of people waiting in line:%d %n",
                          mainThreadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
      
          }
  2. Synchronization and mutual exclusion between processes

    1. FileLock is used to realize synchronization and mutual exclusion between processes. Mutex can be used in case of C and C + +

        /**
         * Synchronization between processes (FileLock file lock, open multiple process instances at the same time, if the locked instance is executing, the subsequent process instances can only wait, of course, tryLock non blocking mode can be used)
         */
        private void testFileLock() {
            File lockFile = new File(System.getProperty("user.dir") + File.separator + "app.lock");
            if (!lockFile.exists()) {
                try {
                    if (!lockFile.createNewFile()) {
                        System.out.printf("Failed to create file:" + lockFile.getAbsolutePath());
                        return;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            try {
    
                FileChannel fileChannel = new FileOutputStream(lockFile).getChannel();
                String jvmName = ManagementFactory.getRuntimeMXBean().getName();
    
                System.out.printf("jvm ProcessName:%s, Prepare to acquire lock ... %n", jvmName);
    
                FileLock lock = fileChannel.lock();//Get file lock
    
    
                for (int i = 0; i <= 100; i++) {
                    try {
                        Thread.sleep(100L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.printf("jvm ProcessName:%s, number:%d %n", jvmName, i);
                }
    
                lock.release();
                fileChannel.close();
    
                System.out.printf("jvm ProcessName:%s, Processing complete, release lock %n", jvmName);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
        }
  3. Synchronization and mutual exclusion between machines (i.e. distributed lock)

    1. Implementation based on DB (using update exclusive lock of DB)

      ---There are also many on the Internet. Here, I take out the example of DB based distributed lock (JAVA similarly) implemented by C ා (principle: there is a t ᦇ locking table, type = lock name, SValue=LOCK unique identification value, RecordTime = lock record time. When obtaining lock, I update it directly according to type = n'lock name 'and SValue=N'. If it is updated by other instances, SValue cannot be empty, then Failed to acquire the lock, and then further determine whether there is an expired lock. If it expires, you can still try to update the acquired lock]

              /// <summary>
              ///Set distributed lock
              /// </summary>
              /// <returns></returns>
              private bool SetDistributedLockBasedDB()
              {
                  bool hasLock = false;
                  try
                  {
                      var sqlDapperUtil = new SqlDapperUtil(Constants.CfgKey_KYLDConnectionName);
                      ////Here, we use the update exclusive lock of DB to ensure that only one priority is executed in concurrency. When one execution succeeds, other subsequent executions will fail because the condition update is not satisfied. When multiple concurrency is achieved, only one can be successfully updated, that is to say, the lock is obtained.
                      hasLock = sqlDapperUtil.ExecuteCommand("update [dbo].[T_Locking]   set SValue=@SValue,RecordTime=getdate()   where SType=N'Lock name' and SValue=N''  ", new { SValue = Lock Unique identification value });
      
                      if (!hasLock) //If the lock is not obtained, it is also necessary to consider that the lock is not released normally after the lock is added, so try to update the lock that has been locked for more than 1 hour again as follows to avoid the invalid lock being locked all the time
                      {
                          hasLock = sqlDapperUtil.ExecuteCommand("update [dbo].[T_Locking]   set SValue=@SValue,RecordTime=getdate()   " +
                                               "where SType = N'Lock name' and SValue <> N'' and RecordTime < DATEADD(hh, -1, getdate())",
                                               new { SValue =  Lock Unique identification value });
                      }
                  }
                  catch (Exception ex)
                  {
                      logger.Error("SetDistributedLockBasedDB Error: " + ex.ToString());
                  }
      
                  return hasLock;
              }
      
      
              /// <summary>
              ///Release distributed lock
              /// </summary>
              private void ReleaseDistributedLockBasedDB()
              {
                  try
                  {
                      var sqlDapperUtil = new SqlDapperUtil(Constants.CfgKey_KYLDConnectionName);
                      sqlDapperUtil.ExecuteCommand("update [dbo].[T_Locking]   set SValue=N'',RecordTime=getdate()   where SType=N'Lock name' and SValue=@SValue", new { SValue = Lock Unique identification value });
                  }
                  catch (Exception ex)
                  {
                      logger.Error("ReleaseDistributedLockBasedDB Error: " + ex.ToString());
                  }
              }
    2. Implementation based on Redis

      Implementation 1 (native Implementation): Correct implementation of Redis distributed lock (Java version)

      Implementation mode 2 (Redlock): RedLock, the official algorithm of Redis distributed lock, and Redisson, the Java version implementation library

    3. Implementation based on Zookeeper

      Zookeeeper distributed lock of java synchronization series

* there are articles listing three implementation schemes at the same time. Please refer to: Three implementation schemes of Java distributed lock

Posted by Revlet on Sun, 19 Jan 2020 21:05:45 -0800