Java source code - experience summary: various lock usage scenarios and details at work

Keywords: Java Interview

No, let's start!

Guiding language

Some interviewers like to ask students to rewrite a new lock after they have finished the principle of lock, and ask them to write the general idea and code logic on the whiteboard. This kind of interview topic is very difficult. Personally, I think its focus is mainly on two parts:

  1. Check how you understand the lock principle. If you haven't read the source code, you can tell the general principle just by looking at the online articles or the test questions on the back, but it's difficult for you to write a lock implementation code on the spot, unless you really read the source code or have lock related project experience;
  2. We don't need to create, we just need to rewrite the existing API in Java lock.

If you have seen the source code, the problem is really simple. You can choose a lock you are familiar with to imitate.

In the lock chapter, we talked about exclusive locks. In this section, we take the shared lock as the case list and customize a shared lock.

1. Demand

Generally, we define locks according to requirements. It is impossible to define locks out of thin air. When it comes to shared locks, you may think of many scenarios. For example, the read locks of shared resources can be shared, for example, the shared access to database links, for example, the number of links on the Socket server can be shared. There are many scenarios, We choose the scenario of sharing access database links to define a lock.

2. Detailed design

It is assumed (the following assumptions are assumed) that our database is a stand-alone mysql and can only withstand 10 links. When creating database links, we use the most original JDBC method. We use an interface to encapsulate the process of creating links with JDBC. The name of this interface is: create link interface.

The overall requirements for sharing and accessing database links are as follows: the maximum number of mysql links added together by all requests cannot exceed 10 (including 10). Once it exceeds 10, an error will be reported directly.

In this context, we have designed the following figure:

The key point of this design is that we can determine whether we can get the mysql link by whether we can get the lock. If we can get the lock, we can get the link, otherwise we will report an error directly.

Next, let's look at the landing Code:

2.1. Define lock  

First, we need to define a lock. When defining, we need two elements:

  1. Definition of lock: synchronizer Sync;
  2. The method of locking and unlocking provided by the lock.

The code implementation of shared lock is as follows:

// Sharing unfair locks
public class ShareLock implements Serializable{
	// Synchronizer
  private final Sync sync;
  // Used to ensure that the maximum value cannot be exceeded
  private final int maxCount;
 
  /**
   * Assign a value to synchronizer sync during initialization
   * count Represents the maximum number of shared locks that can be obtained
   */
  public ShareLock(int count) {
    this.sync = new Sync(count);
    maxCount = count;
  }
 
  /**
   * Acquire lock
   * @return true Indicates that the lock was obtained successfully, and false indicates failure
   */
  public boolean lock(){
    return sync.acquireByShared(1);
  }
 
  /**
   * Release lock
   * @return true Indicates that the lock was released successfully, and false indicates failure
   */
  public boolean unLock(){
    return sync.releaseShared(1);
  }
}  

As can be seen from the above code, the implementation of locking and releasing locks depends on the underlying implementation of synchronizer Sync.

The only thing to note is that API specifications should be specified for locks, mainly in two aspects:

  1. What the API needs is what parameters you need to pass to me during lock initialization. When ShareLock is initialized, you need to pass the maximum number of shareable locks;
  2. You need to define your own capabilities, that is, define the input and output parameters of each method. In the implementation of ShareLock, there are no input parameters for locking and releasing locks. It is a dead 1 written in the method, which means that each time the method is executed, the lock can only be added or released once. The output parameter is a Boolean value, true means that the locking or releasing is successful, false means failure, and the bottom layer uses Sync unfair locks.

The above way of thinking has methodology, that is, when we think about a problem, we can start from two aspects: what is API? What are the capabilities of API?

2.2. Define synchronizer Sync

Sync directly inherits AQS with the following code:

class Sync extends AbstractQueuedSynchronizer {
 
  // Indicates that at most count shared locks can be obtained
  public Sync(int count) {
    setState(count);
  }
 
  // Obtain i locks
  public boolean acquireByShared(int i) {
    // Spin ensures that CAS will succeed
    for(;;){
      if(i<=0){
        return false;
      }
      int state = getState();
      // If no lock can be obtained, return false directly
      if(state <=0 ){
        return false;
      }
      int expectState = state - i;
      // If there are not enough locks to get, return false directly
      if(expectState < 0 ){
        return false;
      }
      // CAS attempts to obtain the lock. CAS successfully obtains the lock. If it fails, continue the for loop
      if(compareAndSetState(state,expectState)){
        return true;
      }
    }
  }
 
  // Release i locks
  @Override
  protected boolean tryReleaseShared(int arg) {
    for(;;){
      if(arg<=0){
        return false;
      }
      int state = getState();
      int expectState = state + arg;
      // The maximum value of int is exceeded, or the expectState exceeds our maximum expectation
      if(expectState < 0 || expectState > maxCount){
        log.error("state More than expected, current state is {},Calculated state is {}",state
        ,expectState);
        return false;
      }
      if(compareAndSetState(state, expectState)){
        return true;
      }
    }
  }
}

The whole code is relatively clear. We need to pay attention to:

  1. Boundary judgment, such as whether the input parameter is illegal or not, and whether the expected state is illegal when releasing the lock. We need to judge such problems to reflect the preciseness of thinking;
  2. Locking and releasing locks need to be in the form of for spin + CAS to ensure that when locking or releasing locks concurrently, they can be retried successfully. When writing for spin, we need to pay attention to return at the appropriate time, so as not to cause an endless loop. The CAS method AQS has provided. Don't write it yourself. The CAS method we write ourselves can't guarantee atomicity.

2.3. Determine whether the link can be obtained by whether the lock can be obtained

After the lock is defined, we need to combine the lock with obtaining Mysql links. We have written a Mysql link tool class called MysqlConnection, which is mainly responsible for two functions:

  1. Establish a link with Mysql through JDBC;
  2. Combined with locks, to prevent excessive requests, the total number of links in Mysql cannot exceed 10.

First, let's look at the initialization code of MysqlConnection:

public class MysqlConnection {
  private final ShareLock lock;
  
  // maxConnectionSize indicates the maximum number of links
  public MysqlConnection(int maxConnectionSize) {
    lock = new ShareLock(maxConnectionSize);
  }
}

We can see that during initialization, we need to determine the maximum number of links, and then pass this value to the lock, because the maximum number of links is the state value of ShareLock lock lock.

Then, in order to complete 1, we write a private method:

// Get a mysql link and omit the underlying implementation
private Connection getConnection(){}

Then we implement 2. The code is as follows:

// Interface for obtaining mysql links externally
// The try finally structure is not used here, and there will be no exception at the bottom of the lock acquisition implementation
// There is no need to release the lock even if an unknown exception occurs
public Connection getLimitConnection() {
  if (lock.lock()) {
    return getConnection();
  }
  return null;
}
 
// Interface for releasing mysql links externally
public boolean releaseLimitConnection() {
  return lock.unLock();
}

The logic is relatively simple. If you lock it, if you get the lock, you can return the Mysql link. When you release the lock, after the link is closed, you can call the releaseLimitConnection method. This method adds the state status of the lock to one, indicating that the link is released.

In the above steps, the scene lock for Mysql link restrictions is completed.

3. Testing

After the lock is written, let's test it. We wrote a test demo. The code is as follows:

public static void main(String[] args) {
  log.info("Imitation begins to gain mysql link");
  MysqlConnection mysqlConnection = new MysqlConnection(10);
  log.info("initialization Mysql The maximum number of links can only be 10");
  for(int i =0 ;i<12;i++){
    if(null != mysqlConnection.getLimitConnection()){
      log.info("Get No{}Database links succeeded",i+1);
    }else {
      log.info("Get No{}Database link failures: database connection pool is full",i+1);
    }
  }
  log.info("Imitation begins to release mysql link");
  for(int i =0 ;i<12;i++){
    if(mysqlConnection.releaseLimitConnection()){
      log.info("Release section{}Database links succeeded",i+1);
    }else {
      log.info("Release section{}Database links failed",i+1);
    }
  }
  log.info("End of imitation");
}

The logic of the above code is as follows:

  1. Get the Mysql link logic: for loop gets the link, 1 ~ 10 can get the link, 11 ~ 12 can't get the link, because the link is used up;
  2. Release lock logic: the for loop releases the link. 1 ~ 10 can be released successfully, and 11 ~ 12 fail to release.

Let's look at the operation results, as shown in the following figure:

From the running results, we can see that the ShareLock lock we implemented has completed the Mysql link sharing scenario.

4. Summary  

After reading this, I wonder if you have two feelings:

  1. Rewriting a lock is really simple. The most important thing is to fit perfectly with the scene. A lock that can meet the business scene is a good lock;
  2. In fact, locks are only used to meet business scenarios. They are essentially AQS. Therefore, as long as AQS is learned, it is not difficult to rewrite locks when the scenarios are well understood.

The core of the lock chapter is the two chapters of AQS source code analysis. As long as we understand AQS, the implementation of other locks can almost immediately know the principle of the underlying implementation by looking at the source code implementation. Most of them complete different scenario requirements by operating state. Therefore, it is recommended that you read more AQS source code and debug more AQS source code, As long as the AQS is clear, the lock is very simple.

No wordy, the end of the article, it is recommended to connect three times!

Posted by weazy on Thu, 07 Oct 2021 17:16:24 -0700