Many partners in learning Java, always feel that Java multithreading is rarely used in the actual business, so that it will not spend too much time to learn, technical debt continues to accumulate! When it comes to a certain extent, it is difficult to understand things related to Java multithreading. Today, we need to discuss things that are the same and related to Java multithreading! Get ready and drive now!

Learned Java multithreading should know what is the lock, did not learn also do not worry, the lock in Java can be simply understood as a thread synchronization mechanism to access critical resources under multithreading.

In learning or using Java, processes will encounter various concepts of locks: fair, unfair, spin, reentranced, biased, lightweight, heavyweight, read/write, mutex, and so on.

Obtained? It doesn’t matter! Even if you these are not also unimportant, because this and today to discuss the relationship is not much, but if you as a love of learning partners, here also to prepare a learning route for you, interested friends can pay attention to my background private letter I [learning] free!!

Why use distributed locks

We are in the development of applications, if the need for a shared variable multithreaded synchronization access, we can use the Java multithreaded 18 like arts for processing, and can be perfect operation, no Bug!

Note that this is a stand-alone application, meaning that all requests are allocated to the current server’s JVM and mapped to the operating system’s thread for processing! The shared variable is just a chunk of memory inside the JVM!

Later, with the development of business, clustering is required. An application needs to be deployed on several machines and then load balancing is performed, as shown in the following figure:

JVM1 JVM2 JVM3 JVM3 JVM3 JVM1 JVM2 JVM3 JVM3 JVM1 JVM2 JVM3 UserController, A member variable of an integer type, allocates memory in the JVM at the same time without any control. Even if they are not sent at the same time, and three requests operate on data from three different JVM memory regions, variable A is not shared or visible, and the result is not correct!

If this scenario exists in our business, we need a way to solve this problem!

To ensure that a method or attribute can only be executed by the same thread at the same time in the case of high concurrency, Java concurrent processing apis (such as ReentrantLock and Synchronized) can be used for mutual exclusion control in the case of single application deployment. In a stand-alone environment, Java provides many apis for concurrent processing. But with the needs of the development of business, the original single standalone deployment system was evolved into the distributed cluster system, due to the distributed system multithreading, multi-process and distribution on different machines, it will make the original single deployment of concurrency control lock strategy fails, pure Java API does not provide the ability of distributed lock. To solve this problem, you need a mutual exclusion mechanism across JVMS to control access to shared resources, and that’s where distributed locking comes in!

Second, distributed lock should have what conditions

Before we look at the three implementations of distributed locks, let’s take a look at the conditions that distributed locks should have:

  1. In distributed systems, a method can only be executed by one thread on a machine at a time.
  2. Highly available lock acquisition and lock release;
  3. High-performance lock acquisition and lock release;
  4. With reentrant characteristics;
  5. Lock failure mechanism to prevent deadlocks;
  6. It has the non-blocking lock feature, that is, if the lock is not obtained, the system returns a failure to obtain the lock.

Three, distributed lock three ways of implementation

At present, almost many large websites and applications are distributed deployment, data consistency in distributed scenarios has always been an important topic. The distributed CAP theory tells us that “no distributed system can satisfy Consistency, Availability and Partition tolerance at most.” As a result, many systems are designed with these three trade-offs. In the vast majority of scenarios in the Field of the Internet, it is necessary to sacrifice strong consistency in exchange for high availability of the system. The system usually only needs to ensure “final consistency”, as long as the final time is acceptable to users.

In many scenarios, in order to ensure the final consistency of data, we need a lot of technical solutions to support, such as distributed transactions, distributed locks and so on. Sometimes we need to ensure that a method can only be executed by the same thread at the same time.

Distributed lock based on database;

Distributed lock based on cache (Redis, etc.);

Distributed lock based on Zookeeper;

Although there are these three schemes, but different businesses should also be selected according to their own situation, there is no best between them only more suitable!

Many of you may not have the resources to review your entire Java body of knowledge, or you may not know where to start. I came across a collection of highly technical materials, both from a Java knowledge base and from an interview perspective. Need friends can private letter I [study] free!

Four, based on database implementation

Based on database is implemented the core idea is: to create a table in database table contains the method name and other fields, and create a unique index on method name field, want to execute a method, use this method to insert the data in the table, successful insertion is acquiring a lock, after completion of execution to delete the corresponding row data releases the lock.

(1) create a table:

 

DROP TABLE IF EXISTS `method_lock`; CREATE TABLE 'method_lock' (' id 'int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', 'desc' varchar(255) NOT NULL COMMENT 'id ',' desc 'varchar(255) NOT NULL COMMENT' id ', 'desc' varchar(255) NOT NULL COMMENT 'id ', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT=' lock ';Copy the code

Select * from (select * from (select * from (select * from (select * from (select * from)));

 

INSERT INTO method_lock (method_name, desc) VALUES ('methodName', 'methodName');Copy the code

Because we have a unique constraint on method_name, if multiple requests are submitted to the database at the same time, the database guarantees that only one operation will succeed, so we can assume that the thread that succeeded has acquired the lock and can execute the method body.

(3) The lock is acquired after successful insertion, and the corresponding row data is deleted after completion to release the lock:

 

delete from method_lock where method_name ='methodName';
Copy the code

Note: This is just one way to use a database-based approach, there are many other ways to use a database for distributed locking!

Using this database-based implementation is simple, but there are some issues that need to be addressed and optimized for what a distributed lock should be:

  1. Because it is implemented based on the database, the availability and performance of the database will directly affect the availability and performance of the distributed lock, so the database needs two-node deployment, data synchronization, master/standby switchover;
  2. Not having the features of reentrant, because the same thread before releasing the lock, row data, unable to successfully insert data again, so, need to add a column in the table, used to record the current access to the machine on the lock and the thread information, at the time of acquiring a lock again, in the first lookup table machine and thread information and the current machine is the same as the thread, if the same direct acquiring a lock;
  3. There is no lock failure mechanism, because it is possible that after data is inserted successfully, the server breaks down and the corresponding data is not deleted. After the service is restored, the server cannot obtain the lock. Therefore, a new column in the table is needed to record the failure time, and a scheduled task is required to clear the failed data.
  4. Does not have the blocking lock feature, can not obtain the lock directly return failure, so need to optimize the acquisition logic, cycle to obtain multiple times.
  5. In the process of implementation will encounter a variety of different problems, in order to solve these problems, the implementation will be more and more complex; Depending on the database requires a certain amount of resource overhead and performance concerns.

Five, based on Redis implementation

1. Reasons for selecting Redis to realize distributed lock:

(1) Redis has high performance;

(2) The Redis command supports this well and is convenient to implement

2. Introduction to using commands:

(1) SETNX

 

SETNX key val: Sets a string with key val if and only if the key does not exist, returning 1; If key exists, do nothing and return 0.Copy the code

(2) the expire

 

Expire key timeout: sets a timeout period for the key. The unit is second. After this timeout period, locks will be released automatically to avoid deadlocks.Copy the code

(3) the delete

 

Delete key: deletes a keyCopy the code

These three commands are mainly used when implementing distributed locks using Redis.

3. Realize ideas:

(1) When obtaining the lock, use setnx to add the lock and use expire command to add a timeout time for the lock. When the timeout time expires, the lock will be automatically released. The value of the lock is a randomly generated UUID, which can be used to determine the lock release.

(2) Set a timeout time for lock acquisition. If the time exceeds this time, lock acquisition will be abandoned.

(3) When releasing the lock, determine whether it is the lock according to the UUID. If it is the lock, execute delete to release the lock.

4, distributed lock simple implementation code:

 

*/ liuyang public class DistributedLock {private final JedisPool * Created by liuyang on 2017/4/20 jedisPool; public DistributedLock(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * lock * @param lockName Lock key * @param acquireTimeout acquireTimeout time * @param timeout lock timeout time * @return lock identifier */ public String  lockWithTimeout(String lockName, long acquireTimeout, long timeout) { Jedis conn = null; String retIdentifier = null; Conn = jedispool.getResource (); // Generate a value String identifier = uuid.randomuuid ().toString(); String lockKey = "lock:" + lockName; Int lockExpire = (int) (timeout / 1000); Long end = system.currentTimemillis () + acquireTimeout; while (System.currentTimeMillis() < end) { if (conn.setnx(lockKey, identifier) == 1) { conn.expire(lockKey, lockExpire); RetIdentifier = identifier; // Return the value used to release the lock. return retIdentifier; If (conn.ttl(lockKey) == -1) {conn.expire(lockKey, lockExpire); } try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (JedisException e) { e.printStackTrace(); } finally { if (conn ! = null) { conn.close(); } } return retIdentifier; } /** * public Boolean releaseLock(String lockName, String lockName, String lockName, String lockName, String lockName) String identifier) { Jedis conn = null; String lockKey = "lock:" + lockName; boolean retFlag = false; try { conn = jedisPool.getResource(); While (true) {// Monitor lock, ready to start transaction conn.watch(lockKey); // Check whether the lock is the same as the value returned earlier. If the lock is the same, delete it. If (identifiers. Equals (conn.get(lockKey))) {Transaction Transaction = conn.multi(); transaction.del(lockKey); List<Object> results = transaction.exec(); if (results == null) { continue; } retFlag = true; } conn.unwatch(); break; } } catch (JedisException e) { e.printStackTrace(); } finally { if (conn ! = null) { conn.close(); } } return retFlag; }}Copy the code

5. Test the distributed lock you just implemented

In this example, 50 threads are used to simulate killing a commodity in seconds. The – operator is used to reduce the commodity, and the order of the result can be seen whether the state is locked.

Simulate a seckill service in which a Jedis thread pool is configured to be passed to a distributed lock for use at initialization.

 

/** * Created by liuyang on 2017/4/20. */ public class Service { private static JedisPool pool = null; private DistributedLock lock = new DistributedLock(pool); int n = 500; static { JedisPoolConfig config = new JedisPoolConfig(); // Set the maximum number of connections config.setmaxTotal (200); // Set the maximum number of idle config.setMaxIdle(8); // Set the maximum waiting time config.setMaxWaitMillis(1000 * 100); Config.settestonborrow (true); // If jedis instances borrow (true), all jedis instances are available; Pool = new JedisPool(config, "127.0.0.1", 6379, 3000); } public void seckill() {String identifier = lock.lockWithTimeout("resource", 5000, 1000);} public void seckill() {String identifier = lock.lockWithTimeout("resource", 5000, 1000); System.out.println(thread.currentThread ().getName() + "lock "); System.out.println(--n); lock.releaseLock("resource", identifier); }}Copy the code

Simulate the thread to execute the SEC kill service:

 

public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { this.service = service; } @Override public void run() { service.seckill(); } } public class Test { public static void main(String[] args) { Service service = new Service(); for (int i = 0; i < 50; i++) { ThreadA threadA = new ThreadA(service); threadA.start(); }}}Copy the code

The result is as follows, the result is ordered:

If you comment out parts that use locks:

 

//String indentifier = lock.lockWithTimeout("resource", 5000, 1000); //String indentifier = lock.lockWithTimeout("resource", 5000, 1000); System.out.println(thread.currentThread ().getName() + "lock "); System.out.println(--n); //lock.releaseLock("resource", indentifier); }Copy the code

As you can see from the results, some are asynchronous:

Six, the implementation method based on ZooKeeper

ZooKeeper is an open source component that provides consistency services for distributed applications. It contains a hierarchical file system directory tree structure, allowing only one unique file name in a directory. The steps for implementing distributed lock based on ZooKeeper are as follows:

(1) create a directory mylock;

(2) Thread A wants to acquire the lock and creates A temporary sequential node in mylock;

(3) Get all the child nodes in myLock directory, and then get the younger sibling node, if there is no, it means that the current thread sequence number is the smallest, get the lock;

(4) Thread B obtains all nodes, determines that it is not the smallest node, and sets to listen on nodes that are smaller than it;

(5) When thread A finishes processing, it will delete its own node. Thread B will monitor the change event and determine whether it is the smallest node. If so, it will obtain the lock.

InterProcessMutex, an open source library for Apache, is a ZooKeeper client. Acquire method is used to acquire the lock, and release method is used to release the lock.

Advantages: With high availability, reentrant, blocking lock features, can solve the failure deadlock problem.

Disadvantages: Performance is not as good as Redis because nodes need to be frequently created and deleted.

Seven,

The above three implementations are not perfect in all situations, so choose the most suitable implementation according to different application scenarios.

In distributed environments, locking resources is sometimes important, such as when a resource is being snapped up, and distributed locking is used to control resources.

In the concrete use, of course, also need to consider many factors, such as selection of timeout, the selection of acquiring a lock time has great influence on concurrent, the implementation of a distributed lock is just a kind of simple implementation, main is a kind of thought, including the code above may not be suitable for formal production environment, introduction to do reference only!

The end of this article, like friends help forward the article and attention, thank you!!