Some interviewers like to ask students to rewrite a new lock after explaining the principle of the lock, and ask them to be present at the scene Write the general idea and code logic on the whiteboard. This kind of interview question is quite difficult. I personally think it focuses on two parts:
Examine how you came to understand the lock principle. , if you have not interpreted the source code, you can just read online articles or memorize interview questions, and you can tell the general principle, but it is difficult for you to write a lock implementation code on the spot, unless you have really read the source code. Or have project experience related to locks;
We don’t need to create, we just need to imitate the existing API in Java locks and rewrite it.
If you have read the source code, this question is really simple. You can choose a lock you are familiar with to imitate.
Generally when customizing locks, we define them based on requirements. It is impossible to define locks out of thin air. When it comes to shared locks, you may think of many things. Scenarios, for example, read locks on shared resources can be shared, such as 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 shared access to database links to define a Lock.
Assume (the following assumptions are all assumptions) that our database is a stand-alone mysql, which can only bear 10 connections. When creating a database connection, we use the original JDBC In this way, we use an interface to encapsulate the process of creating a link using JDBC. We name this interface: Create Link Interface.
The overall requirements for shared access database links are as follows: the number of mysql links for all requests combined cannot exceed 10 (inclusive). Once it exceeds 10, an error will be reported directly.
In this context, we carried out the design as shown below:
The most critical part of this design is whether we can obtain the lock through it. To determine whether the mysql link can be obtained, if the lock can be obtained, then the link can be obtained, otherwise an error will be reported directly.
Then let’s take a look at the implemented code:
First we need to define a lock, which requires two elements:
The definition of lock: synchronizer Sync; the method of locking and unlocking provided by the lock.
The code implementation of the shared lock is as follows:
// 共享不公平锁 public class ShareLock implements Serializable{ // 同步器 private final Sync sync; // 用于确保不能超过最大值 private final int maxCount; /** * 初始化时给同步器 sync 赋值 * count 代表可以获得共享锁的最大值 */ public ShareLock(int count) { this.sync = new Sync(count); maxCount = count; } /** * 获得锁 * @return true 表示成功获得锁,false 表示失败 */ public boolean lock(){ return sync.acquireByShared(1); } /** * 释放锁 * @return true 表示成功释放锁,false 表示失败 */ public boolean unLock(){ return sync.releaseShared(1); } }
As can be seen from the above code, the implementation of locking and releasing locks relies on the underlying implementation of the synchronizer Sync.
The only thing that needs to be noted is that the lock needs to have good API specifications, mainly in two aspects:
What the API requires is what parameters you need to pass to me when the lock is initialized. When initializing ShareLock, you need to pass the maximum number of shareable locks;
You need to define your own capabilities, that is, define the input parameters and output parameters of each method. In the implementation of ShareLock, there are no input parameters for locking and releasing locks. They are hard-coded 1 in the method, which means that each time the method is executed, the lock can only be locked once or released once. The output parameter is a Boolean value, and true means adding The lock or release of the lock is successful, false indicates failure, and the bottom layer uses Sync unfair locks.
The above way of thinking has a methodology, that is, when we think about a problem, we can start from two aspects: What is an API? What capabilities does the API have?
Sync directly inherits AQS, the code is as follows:
class Sync extends AbstractQueuedSynchronizer { // 表示最多有 count 个共享锁可以获得 public Sync(int count) { setState(count); } // 获得 i 个锁 public boolean acquireByShared(int i) { // 自旋保证 CAS 一定可以成功 for(;;){ if(i<=0){ return false; } int state = getState(); // 如果没有锁可以获得,直接返回 false if(state <=0 ){ return false; } int expectState = state - i; // 如果要得到的锁不够了,直接返回 false if(expectState < 0 ){ return false; } // CAS 尝试得到锁,CAS 成功获得锁,失败继续 for 循环 if(compareAndSetState(state,expectState)){ return true; } } } // 释放 i 个锁 @Override protected boolean tryReleaseShared(int arg) { for(;;){ if(arg<=0){ return false; } int state = getState(); int expectState = state + arg; // 超过了 int 的最大值,或者 expectState 超过了我们的最大预期 if(expectState < 0 || expectState > maxCount){ log.error("state 超过预期,当前 state is {},计算出的 state is {}",state ,expectState); return false; } if(compareAndSetState(state, expectState)){ return true; } } } }
The whole code is relatively clear, what we need to pay attention to is:
Boundary judgment, such as whether the input parameters are illegal, whether the expected illegal state will occur when the lock is released, and other boundary issues. We need to judge such issues to reflect the rigor of thinking;
Add Locking and releasing locks need to be in the form of for spin CAS to ensure that retry can be successful when locking or releasing locks concurrently. When writing for spin, we need to pay attention to return at the appropriate time and not to cause an infinite loop. The CAS method has been provided by AQS. Don't write it yourself. The CAS method we write ourselves cannot guarantee atomicity.
After the lock is defined, we need to combine the lock with obtaining the Mysql link. We wrote a Mysql link tool class , called MysqlConnection, which is mainly responsible for two major functions:
Establishing a link with Mysql through JDBC;
Combined with locks to prevent the total number of Mysql links from exceeding 10 when the request is too large .
First let’s take a look at the MysqlConnection initialization code:
public class MysqlConnection { private final ShareLock lock; // maxConnectionSize 表示最大链接数 public MysqlConnection(int maxConnectionSize) { lock = new ShareLock(maxConnectionSize); } }
We can see that during initialization, we need to specify the maximum number of links, and then pass this value to the lock, because the maximum The number of links is the state value of the ShareLock lock.
Then in order to complete 1, we wrote a private method:
// 得到一个 mysql 链接,底层实现省略 private Connection getConnection(){}
Then we implemented 2, the code is as follows:
// 对外获取 mysql 链接的接口 // 这里不用try finally 的结构,获得锁实现底层不会有异常 // 即使出现未知异常,也无需释放锁 public Connection getLimitConnection() { if (lock.lock()) { return getConnection(); } return null; } // 对外释放 mysql 链接的接口 public boolean releaseLimitConnection() { return lock.unLock(); }
逻辑也比较简单,加锁时,如果获得了锁,就能返回 Mysql 的链接,释放锁时,在链接关闭成功之后,调用 releaseLimitConnection 方法即可,此方法会把锁的 state 状态加一,表示链接被释放了。
以上步骤,针对 Mysql 链接限制的场景锁就完成了。
锁写好了,接着我们来测试一下,我们写了一个测试的 demo,代码如下:
public static void main(String[] args) { log.info("模仿开始获得 mysql 链接"); MysqlConnection mysqlConnection = new MysqlConnection(10); log.info("初始化 Mysql 链接最大只能获取 10 个"); for(int i =0 ;i<12;i++){ if(null != mysqlConnection.getLimitConnection()){ log.info("获得第{}个数据库链接成功",i+1); }else { log.info("获得第{}个数据库链接失败:数据库连接池已满",i+1); } } log.info("模仿开始释放 mysql 链接"); for(int i =0 ;i<12;i++){ if(mysqlConnection.releaseLimitConnection()){ log.info("释放第{}个数据库链接成功",i+1); }else { log.info("释放第{}个数据库链接失败",i+1); } } log.info("模仿结束"); }
以上代码逻辑如下:
获得 Mysql 链接逻辑:for 循环获取链接,1~10 都可以获得链接,11~12 获取不到链接,因为链接被用完了;释放锁逻辑:for 循环释放链接,1~10 都可以释放成功,11~12 释放失败。
我们看下运行结果,如下图:
从运行的结果,可以看出,我们实现的 ShareLock 锁已经完成了 Mysql 链接共享的场景了。
The above is the detailed content of What are the design structure and details of Java rewrite lock. For more information, please follow other related articles on the PHP Chinese website!