author :goodlook0123
https://blog.csdn.net/goodlook0123
An interview often involves writing an example of a rush purchase , Or talk about the realization of the idea of flash buying , Then I always don't understand , Because I haven't done the rush purchase at present .
But the idea has finally come to light today , It also refers to the practice of some big men .
This article uses redis, The notes are very clear , Go straight to the code :
junit Test class :
Log log = LogFactory.getLog(getClass());
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
public void testSys() throws Exception{
log.info(" Start ");
MsService service = new MsService();
for (int i = 0; i < 10; i++) {
ThreadB threadA = new ThreadB(service, redisTemplate, "MSKEY");
threadA.start();
log.info("******************* end ");
}
}
threadB class :
private MsService service;
private RedisTemplate<String,Object> redisTemplate;
private String key;
public ThreadB(MsService service,RedisTemplate<String,Object> redisTemplate,String key) {
this.service = service;
this.redisTemplate=redisTemplate;
this.key=key;
}
@Override
public void run() {
try {
service.seckill(redisTemplate, key);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
msService class :
Log log = LogFactory.getLog(getClass());
/***
* Flash code
* @param redisTemplate
* @param key pronum First set the number with the client
* @return
* @throws InterruptedException
*/
public boolean seckill(RedisTemplate<String,Object> redisTemplate, String key) throws Exception {
RedisLock lock = new RedisLock(redisTemplate, key, 10000, 20000);
try {
if (lock.lock()) {
// Code that needs to be locked
String pronum=lock.get("pronum");
// Modify inventory
if(Integer.parseInt(pronum)-1>=0) {
lock.set("pronum",String.valueOf(Integer.parseInt(pronum)-1));
log.info(" Inventory quantity :"+pronum+" success !!!"+Thread.currentThread().getName());
}else {
log.info(" It's been robbed , Please take part in the next rush ");
}
log.info("++++++++++++++++++++++++++++++++++++++ Participated in the rush to buy ");
return true;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// In order to make the distributed lock algorithm more stable , The client holding the lock should check again whether its lock has timed out before unlocking , Do it again DEL operation , Because it is possible that the client is suspended due to a time-consuming operation ,
// At the end of the operation, the lock has been acquired by someone else due to timeout , There's no need to unlock . ———— It's not done here
lock.unlock();
}
return false;
}
redisLock class :
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
private RedisTemplate<String,Object> redisTemplate;
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
/**
* Lock key path.
*/
private String lockKey;
/**
* Lock timeout , Prevent the thread from entering the lock , Infinite execution waiting
*/
private int expireMsecs = 60 * 1000;
/**
* Lock wait time , Prevent thread starvation
*/
private int timeoutMsecs = 10 * 1000;
private volatile boolean locked = false;
/**
* Detailed constructor with default acquire timeout 10000 msecs and lock
* expiration of 60000 msecs.
*
* @param lockKey
* lock key (ex. account:1, ...)
*/
public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
}
/**
* Detailed constructor with default lock expiration of 60000 msecs.
*
*/
public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey, int timeoutMsecs) {
this(redisTemplate, lockKey);
this.timeoutMsecs = timeoutMsecs;
}
/**
* Detailed constructor.
*
*/
public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
this(redisTemplate, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
/**
* @return lock key
*/
public String getLockKey() {
return lockKey;
}
public String get(final String key) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] data = connection.get(serializer.serialize(key));
connection.close();
if (data == null) {
return null;
}
return serializer.deserialize(data);
}
});
} catch (Exception e) {
logger.error("get redis error, key : {}", key);
}
return obj != null ? obj.toString() : null;
}
public String set(final String key,final String value) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
connection.set(serializer.serialize(key), serializer.serialize(value));
return serializer;
}
});
} catch (Exception e) {
logger.error("get redis error, key : {}", key);
}
return obj != null ? obj.toString() : null;
}
public boolean setNX(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
connection.close();
return success;
}
});
} catch (Exception e) {
logger.error("setNX redis error, key : {}", key);
}
return obj != null ? (Boolean) obj : false;
}
private String getSet(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
connection.close();
return serializer.deserialize(ret);
}
});
} catch (Exception e) {
logger.error("setNX redis error, key : {}", key);
}
return obj != null ? (String) obj : null;
}
/**
* get lock. Realize the idea : Mainly used redis Of setnx command , Cache lock . reids The cache key It's locked key, All the sharing ,
* value It's the expiration time of the lock ( Be careful : Let's put the expiration date here value 了 , There's no time to set its timeout ) Execution process :
* 1. adopt setnx Try to set something up key Value , success ( There is no such lock at present ) Then return to , Successful lock acquisition
* 2. Get the expiration time of the lock if the lock already exists , Compare it to the current time , If the timeout , Then set the new value
*
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException
* in case of thread interruption
*/
public synchronized boolean lock() throws InterruptedException {
int timeout = timeoutMsecs;
while (timeout >= 0) {
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); // Lock expiration time
if (this.setNX(lockKey, expiresStr)) {
// lock acquired
locked = true;
return true;
}
String currentValueStr = this.get(lockKey); // redis Time in
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
// Determine whether it is null , Not empty , If the value is set by another thread , Then the second condition is not to pass
// lock is expired
String oldValueStr = this.getSet(lockKey, expiresStr);
// Get last lock expiration time , And set the current lock expiration time ,
// Only one thread can get the setting time of the previous line , because jedis.getSet It's synchronous
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
// Prevent false deletion ( Cover , because key It's the same ) Someone else's lock —— It doesn't work here , Here the value will be overridden , But because there is little time difference , So it's acceptable
// [ In the case of distributed ]: If after this time , Multiple threads happen to be here , But only one thread has the same set value as the current one , He has the right to a lock
// lock acquired
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
/*
* Delay 100 millisecond , It might be better to use random time here , Can prevent the emergence of hunger process , namely , When multiple processes arrive at the same time ,
* Only one process gets the lock , The others are trying on the same frequency , There's some going on in the back , Apply for locks at the same frequency , This may result in the front lock not being satisfied .
* Using random waiting time can guarantee fairness to some extent
*/
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
return false;
}
/**
* Acqurired lock release.
*/
public synchronized void unlock() {
if (locked) {
redisTemplate.delete(lockKey);
locked = false;
}
}
This is based on redis It's easy to implement . If you think there is something wrong, please correct , Little brother can also improve a little bit .
Recommended reading
Code comparison tool , I'll use this 6 individual
Share my favorite 5 A free online SQL Database environment , It's so convenient !
Spring Boot A combination of three moves , Hand in hand to teach you to play elegant back-end interface
MySQL 5.7 vs 8.0, You choose the one ? Net friend : I'm going to stay where I am ~
Last , I recommend you an interesting and interesting official account : The guy who wrote the code ,7 Old programmers teach you to write bug, reply interview | resources Send you a complete set of Development Notes There's a surprise