什麼是自旋鎖 自旋鎖是指當一個線程嘗試擷取某個鎖時,如果該鎖已被其他線程占用,就一直循環檢測鎖是否被釋放,而不是進入線程挂起或睡眠狀态。
/**
* 為什麼用自旋鎖:多個線程對同一個變量一直使用CAS操作,那麼會有大量修改操作,
* 進而産生大量緩存一緻性流量,因為每一次CAS操作都會發出廣播通知其他處理器,進而影響程式的性能。
*
* 自旋不是阻塞,阻塞被喚醒的代價高,性能較差。自旋是執行空代碼,雖然效率高,但是會一直占用CPU,
* 最好是很短時間内獲得。
*
* 自旋鎖:從等待到解鎖,線程一直處于running狀态,沒有上下文切換。
* 注意死遞歸、死循環導緻死鎖。
*/
自旋鎖+redis
1、導入redis鎖
com.baomidou
lock4j-redisson-spring-boot-starter
2.2.2
2、業務實作
自旋鎖+redis
1、導入redis鎖
com.baomidou
lock4j-redisson-spring-boot-starter
2.2.2
2、業務實作
@Slf4j
@RunWith(SpringRunner.class)
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TaskApplication.class)
public class ExceptionTest {
@Resource
private LockTemplate lockTemplate;
private final String key = "work_redis_default_lock_test";
@Test
public void test() {
LockInfo lockInfo = null;
try {
//請求逾時時間小于等于0L,架構會用預設的請求逾時時間,第四個參數決定lock方法用架構裡面的自旋鎖。設定逾時時間。請求時間和過期時間是不一樣的概念。
lockInfo = lockTemplate.lock(key, 50000,
0L, RedissonLockExecutor.class);
if (null == lockInfo) {
log.warn("未搶到鎖,結束, key:{}", key);
return;
}
//模拟業務
long startTime = System.currentTimeMillis();
Thread.sleep(5000);
long endTime = System.currentTimeMillis();
log.info("startTime:{}, endTime:{}, user time:{}", startTime, endTime, endTime - startTime);
} catch (Exception e) {
log.error("e:", e);
} finally {
//釋放鎖
lockTemplate.releaseLock(lockInfo);
}
}
}
3、結果
2022-07-26 16:47:28.920 INFO 23188 — [ main] ExceptionTest : startTime:1658825243919, endTime:1658825248920, user time:5001
4、dubug
在dubug到這裡的時候,實際上,redis已經加了鎖。
key是我們定義的key,裡面的key類型是hash,key随機産生,value 1。
上鎖,其他人無法進來,在外面等待。
設定逾時、過期時間,如果中途中斷了,這個key也會過期,這樣不會影響到其他線程通路,是防止死鎖的重要手段。
最後執行釋放鎖,那redis key會被銷毀。
5、源碼研究
public LockInfo lock(String key, long expire, long acquireTimeout, Class<? extends LockExecutor> executor) {
acquireTimeout = acquireTimeout < 0L ? this.properties.getAcquireTimeout() : acquireTimeout;
long retryInterval = this.properties.getRetryInterval();
LockExecutor lockExecutor = this.obtainExecutor(executor);
log.debug(String.format(“use lock class: %s”, lockExecutor.getClass()));
expire = !lockExecutor.renewal() && expire <= 0L ? this.properties.getExpire() : expire;
int acquireCount = 0;
String value = LockUtil.simpleUUID();
long start = System.currentTimeMillis();
try {
do {
++acquireCount;
Object lockInstance = lockExecutor.acquire(key, value, expire, acquireTimeout);
if (null != lockInstance) {
return new LockInfo(key, value, expire, acquireTimeout, acquireCount, lockInstance, lockExecutor);
}
TimeUnit.MILLISECONDS.sleep(retryInterval);
} while(System.currentTimeMillis() - start < acquireTimeout);
return null;
} catch (InterruptedException var15) {
log.error("lock error", var15);
throw new LockException();
}
}
實際上這裡在執行自旋鎖,如果該鎖已被其他線程占用,就一直循環檢測鎖是否被釋放,而不是進入線程挂起或睡眠狀态。
do {
++acquireCount;
Object lockInstance = lockExecutor.acquire(key, value, expire, acquireTimeout);
if (null != lockInstance) {
return new LockInfo(key, value, expire, acquireTimeout, acquireCount, lockInstance, lockExecutor);
}
TimeUnit.MILLISECONDS.sleep(retryInterval);
} while(System.currentTimeMillis() - start < acquireTimeout);
自旋鎖裡面getlock trylock擷取鎖
public RLock acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
try {
RLock lockInstance = this.redissonClient.getLock(lockKey);
boolean locked = lockInstance.tryLock(acquireTimeout, expire, TimeUnit.MILLISECONDS);
return (RLock)this.obtainLockInstance(locked, lockInstance);
} catch (InterruptedException var9) {
return null;
}
}