一、鎖
1.1 什么是鎖?
在JAVA中是一個非常重要的概念,尤其是在當今的互聯網時代,高并發的場景下,更是離不開鎖,那么鎖到底是什么呢?在計算機科學中,鎖(lock)或互斥(mutex)是一種同步機制,用于在有許多執行執行緒的環境中強制對資源的訪問限制,鎖旨在強制實施互斥排他、并發控制策略,
舉一個生活中的例子:大家都去過超市買東西,如果你隨身帶了包呢,要放到儲物柜里,咱們把這個例子再極端一下,假如柜子只有一個,現在同時來了3個人A,B,C,都要往這個柜子里放東西,這個場景就構造了一個多執行緒,多執行緒自然離不開鎖,如下圖所示:

A,B,C都要往柜子里放東西,可是柜子只能放一件東西,那怎么辦呢?這個時候呢就引出了鎖的概念,3個人中誰搶到了柜子的鎖,誰就可以使用這個柜子,其他的人只能等待,比如:C搶到了鎖,C可以使用這個柜子,A和B只能等待,等C使用完了,釋放鎖以后,A和B再爭搶鎖,誰搶到了,再繼續使用柜子,
1.2 代碼演示
-
創建柜子Cabinet類
public class Cabinet { //柜子中存盤的數字 private int storeNumber; public int getStoreNumber() { return storeNumber; } public void setStoreNumber(int storeNumber) { this.storeNumber = storeNumber; } } -
創建用戶UserInfo類,其中
public class UserInfo { //柜子 private Cabinet cabinet; //存盤的數字 private int storeNumber; public UserInfo(Cabinet cabinet, int storeNumber) { this.cabinet = cabinet; this.storeNumber = storeNumber; } public void useCabinet(){ cabinet.setStoreNumber(storeNumber); } } -
啟動類模擬3個用戶使用柜子的場景:
public class Starter { public static void main(String[] args) { final Cabinet cabinet = new Cabinet(); ExecutorService es = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { final int storeNumber = i; es.execute(() -> { UserInfo users = new UserInfo(cabinet, storeNumber); synchronized (cabinet) { users.useCabinet(); System.out.println("我是用戶" + storeNumber + ",我存盤的數字是:" + cabinet.getStoreNumber()); } }); } es.shutdown(); } }
1.當柜子沒有加鎖的時候,3個用戶并行的執行,向柜子中存盤了他們的數字,雖然3個用戶并行的同時操作,但在具體賦值的時候也是有順序的,因為setStoreNumber只占有一塊記憶體,storeNumber只存盤最后的執行緒所設定的值,至于那個執行緒排在最后是不確定的,所以在列印storeNumber取值時,3個執行緒取到的值是相同的,
2.如果在設定storeNumber的方法上加上synchronized關鍵字,這樣在存盤數字的時候,就不會并行的去執行了,而是哪個用戶搶到鎖,哪個用戶執行存盤數字的方法,但是由于存盤陳述句和列印方法是兩個陳述句,并沒有保證原子性,雖然在set方法上加了鎖,但是在列印的時候又會產生一個并發,列印陳述句是有鎖的,但是不能確定哪個執行緒去執行,所以這里,我們要保證useCabinet和列印的方法的原子性,就必須使用synchronized塊,并且由于每個執行緒都初始化了user,總共有3個user物件了,而cabinet物件只有一個,我們使用synchronized塊里的物件要用cabinet,
具體圖例如下:

1.3 常用鎖
JAVA為我們提供了種類豐富的鎖,每種鎖都有不同的特性,鎖的使用場景也各不相同,在這里會通過鎖的定義,核心代碼剖析,以及使用場景來給大家介紹JAVA中主流的幾種鎖,
public class Test {
private int i=0;
public static void main(String[] args) {
Test test = new Test();
ExecutorService es = Executors.newFixedThreadPool(50);
CountDownLatch cdl = new CountDownLatch(5000);
for (int i = 0;i < 5000; i++){
es.execute(()->{
test.i++;
cdl.countDown();
});
}
es.shutdown();
try {
//等待5000個任務執行完成后,列印出執行結果
cdl.await();
System.out.println("執行完成后,i="+test.i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面的程式中,我們模擬了50個執行緒同時執行i++,總共執行5000次,按照常規的理解,得到的結果應該是5000,我們運行一下程式,看看執行的結果如何?
執行完成后,i=4975
執行完成后,i=4986
執行完成后,i=4971
這是我們運行3次以后得到的結果,可以看到每次執行的結果都不一樣,而且不是5000,這是為什么呢?這就說明i++并不是一個原子性的操作,在多執行緒的情況下并不安全,我們把i++的詳細執行步驟拆解一下:
- 從記憶體中取出i的當前值;
- 將i的值加1;
- 將計算好的值放入到記憶體當中;
在多執行緒的場景下,我們可以想象一下,執行緒A和執行緒B同時從記憶體取出i的值,假如i的值是1000,然后執行緒A和執行緒B再同時執行+1的操作,然后把值再放入記憶體當中,這時,記憶體中的值是1001,而我們期望的是1002,正是這個原因導致了上面的錯誤,那么我們如何解決呢?
1.3.1 樂觀鎖
樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到并發寫的可能性低,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,采取在寫時先讀出當前版本號,然后加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重復讀-比較-寫的操作,
java中的樂觀鎖一般會使用“資料版本機制”或“CAS操作”來實作,
-
實作資料版本一般有兩種,第一種是使用版本號,第二種是使用時間戳,以版本號方式為例,
版本號方式:一般是在資料表中加上一個資料版本號version欄位,表示資料被修改的次數,當資料被修改時,version值會加一,當執行緒A要更新資料值時,在讀取資料的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前資料庫中的version值相等時才更新,否則重試更新操作,直到更新成功,
核心SQL代碼:update table set xxx=#{xxx}, version=version+1 where id=#{id} and version=#{version};
-
CAS操作
CAS(Compare and Swap 比較并交換),當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試,
CAS操作中包含三個運算元——需要讀寫的記憶體位置(V)、進行比較的預期原值(A)和擬寫入的新值(B),如果記憶體位置V的值與預期原值A相匹配,那么處理器會自動將該位置值更新為新值B,否則處理器不做任何操作,
在JAVA1.5以后,JDK官方提供了大量的原子類,這些類的內部都是基于CAS機制的,也就是使用了樂觀鎖,
我們將上面的程式稍微改造一下,如下:
public class Test {
private AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) {
Test test = new Test();
ExecutorService es = Executors.newFixedThreadPool(50);
CountDownLatch cdl = new CountDownLatch(5000);
for (int i = 0;i < 5000; i++){
es.execute(()->{
test.i.incrementAndGet();
cdl.countDown();
});
}
es.shutdown();
try {
cdl.await();
System.out.println("執行完成后,i="+test.i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我們將變數i的型別改為AtomicInteger,AtomicInteger是一個原子類,我們在之前呼叫i++的地方改成了i.incrementAndGet(),incrementAndGet()方法采用了CAS機制,也就是說使用了樂觀鎖,
1.3.2 悲觀鎖
悲觀鎖與樂觀鎖恰恰相反,悲觀鎖從讀取資料的時候就加了鎖,而且在更新資料的時候,保證只有一個執行緒在執行更新操作,在這期間只能有一個執行緒去操作,其他的執行緒只能等待,沒有像樂觀鎖那樣進行資料版本的比較,所以悲觀鎖適用于讀相對少,寫相對多的操作,
在JAVA中,悲觀鎖可以使用synchronized關鍵字或者ReentrantLock類來實作,還是上面的例子,我們分別使用這兩種方式來實作一下,
首先是使用synchronized關鍵字來實作:
public class Test {
private int i=0;
public static void main(String[] args) {
Test test = new Test();
ExecutorService es = Executors.newFixedThreadPool(50);
CountDownLatch cdl = new CountDownLatch(5000);
for (int i = 0;i < 5000; i++){
es.execute(()->{
//修改部分 開始
synchronized (test){
test.i++;
}
//修改部分 結束
cdl.countDown();
});
}
es.shutdown();
try {
cdl.await();
System.out.println("執行完成后,i="+test.i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
接下來,我們再使用ReentrantLock類來實作悲觀鎖,代碼如下:
public class Test {
//添加了ReentrantLock鎖
Lock lock = new ReentrantLock();
private int i=0;
public static void main(String[] args) {
Test test = new Test();
ExecutorService es = Executors.newFixedThreadPool(50);
CountDownLatch cdl = new CountDownLatch(5000);
for (int i = 0;i < 5000; i++){
es.execute(()->{
//加鎖
test.lock.lock();
test.i++;
//釋放鎖
test.lock.unlock();
cdl.countDown();
});
}
es.shutdown();
try {
cdl.await();
System.out.println("執行完成后,i="+test.i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我們在類中顯示的增加了Lock lock = new ReentrantLock();,而且在i++之前增加了lock.lock(),加鎖操作,在i++之后增加了lock.unlock()釋放鎖的操作,
1.3.2 公平鎖和非公平鎖
-
公平鎖是指多個執行緒按照申請鎖的順序來獲取鎖,
-
非公平鎖是指多個執行緒獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的執行緒比先申請的執行緒優先獲取鎖,有可能,會造成優先級反轉或者饑餓現象,非公平鎖的優點在于吞吐量比公平鎖大,
對于Java ReetrantLock而言,通過建構式指定該鎖是否是公平鎖,默認是非公平鎖,
對于Synchronized而言,也是一種非公平鎖,
公平鎖與非公平鎖都在ReentrantLock類里給出了實作,我們看一下ReentrantLock的原始碼,
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock有兩個構造方法,默認的構造方法中,sync = new NonfairSync();我們可以從字面意思看出它是一個非公平鎖,再看看第二個構造方法,它需要傳入一個引數,引數是一個布爾型,true是公平鎖,false是非公平鎖,從上面的原始碼我們可以看出sync有兩個實作類,分別是FairSync和NonfairSync,我們再看看獲取鎖的核心方法,首先是公平鎖FairSync的:
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
然后是非公平鎖NonfairSync的:
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
通過對比兩個方法,我們可以看出唯一的不同之處在于!hasQueuedPredecessors()這個方法,很明顯這個方法是一個佇列,由此可以推斷,公平鎖是將所有的執行緒放在一個佇列中,一個執行緒執行完成后,從佇列中取出下一個執行緒,而非公平鎖則沒有這個佇列,這些都是公平鎖與非公平鎖底層的實作原理,我們在使用的時候不用追到這么深層次的代碼,只需要了解公平鎖與非公平鎖的含義,并且在呼叫構造方法時,傳入true和false即可,
二、分布式鎖
在說分布式鎖之前,我們看一看單體應用鎖的特點,單體應用鎖是在一個JVM行程內有效,無法跨JVM、跨行程,那么分布式鎖的定義就出來了,分布式鎖就是可以跨越多個JVM、跨越多個行程的鎖,這種鎖就叫做分布式鎖,

分布式鎖都是通過第三方組件來實作的,目前比較流行的分布式鎖的解決方案有:
- 資料庫,通過資料庫可以實作分布式鎖,但是在高并發的情況下對資料庫壓力較大,所以很少使用,
- Redis,借助Redis也可以實作分布式鎖,而且Redis的Java客戶端種類很多,使用的方法也不盡相同,
- Zookeeper,Zookeeper也可以實作分布式鎖,同樣Zookeeper也存在多個Java客戶端,使用方法也不相同,
具體實作方法的代碼實作及優缺點如下:
| 方式 | 優點 | 缺點 |
|---|---|---|
| 資料庫 | 實作簡單、易于理解 | 對資料庫壓力大 |
| Redis | 易于理解 自己實作 | 不支持阻塞 |
| Zookeeper | 支持阻塞 | 需理解Zookeeper、程式復雜 |
| Curator(推薦) | 提供鎖的方法 | 依賴Zookeeper、強一致 |
| Redisson(推薦) | 提供鎖的方法,可阻塞 |
2.1 基于資料庫的分布式鎖
2.1.1原理
- 多個行程、多個執行緒訪問共同組建資料庫
- 通過select…for update訪問同一條資料
- for updatet鎖定資料,其他執行緒只能等待
2.1.2 代碼
//實體
@Data
public class DistributeLock {
private Integer id;
private String businessCode;
private String businessName;
}
//api介面
@Slf4j
@RestController
public class DemoController {
@GetMapping("singleLock")
@Transactional(rollbackOn = Exception.class)
public String singleLock() throws Exception {
log.info("我進入了方法");
DistributeLock distributeLock = distributeLockMapper.selectDistributeLock("demo");
if (distributeLock == null) {
throw new Exception("分步式鎖找不到");
}
log.info("我進入了鎖");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "執行完成";
}
}
//DistributeLockMapper.xml配置類中添加如下sql陳述句
<select id="selectDistributeLock" resultType="com.example.distributelock.model.DistributeLock">
select * from distribute_lock where business_code = #{businessCode,jdbcType=VARCHAR} for update
</select>
//DistributeLockMapper檔案中添加selectDistributeLock方法,和上述sql中id同名
@Component
public interface DistributeLockMapper {
DistributeLock selectDistributeLock(@Param("businessCode")String businessCode);
}
//在application.yml中添加如下配置,mapper類和mapper.xml不在同一個路徑下時,用mapper-locations指定mapper.xml的路徑
mybatis:
mapper-locations: /mybatis/*.xml
2.2 基于redis的setnx實作分布式鎖
2.2.1 原理
獲取鎖的redis命令 set resource_name my_random_value NX PX 30000
- resource_name: 資源名稱,可根據不同的業務區分不同的鎖
- my_random_value:亂數,每個執行緒的隨機值都不同,用于釋放鎖時的校驗
- NX: key不存在時設定成功,key存在時設定不成功
- PX: 自動失效時間,出現例外情況,鎖可以過期失效
利用NX的原子性,多個執行緒并發時,只有一個執行緒可以設定成功,設定成功及獲得鎖,可以執行后續的業務處理,如果出現了例外,過了鎖的有效期,鎖會自動釋放,釋放鎖采用Redis的delete命令,釋放鎖時校驗之前設定的亂數,相同才能釋放,釋放鎖采用LUA腳本的方式實作,具體的流程如下圖:

2.2.2 代碼
//實作類
@Slf4j
public class RedisLock implements AutoCloseable {
private RedisTemplate redisTemplate;
private String key;
private String value;
private int expireTime;
public RedisLock(RedisTemplate redisTemplate, String key, int expireTime) {
this.redisTemplate = redisTemplate;
this.key = key;
this.value = UUID.randomUUID().toString();
this.expireTime = expireTime;
}
public boolean getLock() {
RedisCallback<Boolean> redisCallback = connection -> {
RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
Expiration expiration = Expiration.seconds(expireTime);
byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
return result;
};
return (Boolean) redisTemplate.execute(redisCallback);
}
public boolean unlock() {
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
List<String> keys = Arrays.asList(key);
Boolean result = (Boolean) redisTemplate.execute(redisScript, keys, value);
log.info("釋放鎖的結果:"+result);
return result;
}
@Override
public void close() throws Exception {
unlock();
}
}
//api
@RestController
@Slf4j
public class RedisLockController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("redisLock")
public String redisLock() {
log.info("我進入了方法!");
try (RedisLock redisLock = new RedisLock(redisTemplate, "redisKey", 30)) {
if (redisLock.getLock()) {
log.info("我進入了鎖");
Thread.sleep(15000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
log.info("方法執行完成");
return "方法執行完成";
}
}
2.2.3 基于分布式鎖解決定時任務重復問題
1.在啟動類上添加@EnableScheduling注解
2.使用@Scheduled注解,開啟定時任務
@Service
@Slf4j
public class SchedulerService {
@Autowired
private RedisTemplate redisTemplate;
@Scheduled(cron = "0/5 * * * * ?")
public void sendSms() {
try (RedisLock redisLock = new RedisLock(redisTemplate, "autoSms", 30)) {
if (redisLock.getLock()) {
log.info("向138xxxxxxxx發送短信!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 zookeeper分布式鎖代碼實作
2.3.1 原理
利用zookeeper瞬時有序節點的特性:
- 多執行緒并發創建瞬時節點時,得到有序的序列,
- 序號最小的執行緒獲得鎖,其他執行緒監聽自己序號的前一個序號,
- 前一個執行緒執行完成,洗掉自己序號的節點,
- 下一個序號的執行緒得到通知,繼續執行,
- 以此類推,
- 創建節點時,已經確定了執行緒的執行順序,
2.3.2 代碼
1.使用docker啟動zookeeper
docker run -d --name=zoo4 -p 2181:2181 -v /mydata/zookeeper/zoo4/data:/data -v /mydata/zookeeper/zoo4/datalog/datalog zookeeper

2.實作類
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {
private ZooKeeper zooKeeper;
private String zNode;
public ZkLock() throws IOException {
this.zooKeeper = new ZooKeeper("47.94.93.93:2181", 10000, this);
}
public boolean getLock(String businessCode) {
try {
//創建業務 根節點
Stat stat = zooKeeper.exists("/" + businessCode, false);
if (stat == null) {
zooKeeper.create("/" + businessCode,
businessCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
//創建瞬時有序節點
zNode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_",
businessCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
//獲取業務節點下所有的子節點
List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);
//子節點排序
Collections.sort(childrenNodes);
//獲取序號最小的(第一個)節點
String firstNode = childrenNodes.get(0);
//如果創建的節點是第一個子節點,則獲得鎖
if (zNode.endsWith(firstNode)) {
return true;
}
//如果不是第一個節點,則監聽前一個節點
String lastNode = firstNode;
for (String node : childrenNodes) {
if (zNode.endsWith(node)) {
zooKeeper.exists("/" + businessCode + "/" + lastNode, true);
break;
} else {
lastNode = node;
}
}
synchronized (this) {
wait();
}
return true;
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public void close() throws Exception {
zooKeeper.delete(zNode, -1);
zooKeeper.close();
log.info("我已經釋放了鎖");
}
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
synchronized (this) {
notify();
}
}
}
}
3.api
@RestController
@Slf4j
public class ZookeeperController {
@RequestMapping("zkLock")
public String zookeeperLock(){
log.info("進入了方法!");
try(ZkLock zkLock = new ZkLock()){
if(zkLock.getLock("order")){
log.info("我獲取了鎖");
Thread.sleep(10000);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
log.info("方法執行完成");
return "方法執行完成";
}
}
4.maven
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
2.4 基于curator分布式鎖
2.4.1 原理
- Curator實質上是基于Zookeeper實作的分布式鎖方案,
- 引入Curator客戶端,Curator已經實作了分布式鎖的方法,使用的時候直接呼叫即可
2.4.2 代碼
1.啟動類
@SpringBootApplication
public class DistributeZkLockApplication {
public static void main(String[] args) {
SpringApplication.run(DistributeZkLockApplication.class, args);
}
@Bean(initMethod="start",destroyMethod = "close")
public CuratorFramework getCuratorFramework() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("47.94.93.93:2181", retryPolicy);
return client;
}
}
2.api
@RestController
@Slf4j
public class ZookeeperController {
@RequestMapping("curatorLock")
public String curatorLock(){
log.info("我進入了方法!");
InterProcessMutex lock = new InterProcessMutex(client, "/order");
try {
if (lock.acquire(30, TimeUnit.SECONDS)) {
log.info("我獲得了鎖!!");
Thread.sleep(10000);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
log.info("我釋放了鎖!!");
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
log.info("方法執行完成!");
return "方法執行完成!";
}
}
3.maven
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
2.5 基于Redisson實作分布式鎖
2.5.1 Redisson簡介
Redisson是架設在Redis基礎上的一個Java駐記憶體資料網格(In-Memory Data Grid),
Redisson主要適用于以下幾種場景:
分布式應用,快取,分布式會話,分布式任務/服務/延遲執行服務,Redis客戶端
Redisson在基于NIO的Netty框架上,充分的利用了Redis鍵值資料庫提供的一系列優勢,在Java實用工具包中常用介面的基礎上,為使用者提供了一系列具有分布式特性的常用工具類,使得原本作為協調單機多執行緒并發程式的工具包獲得了協調分布式多機多執行緒并發系統的能力,大大降低了設計和研發大規模分布式系統的難度,同時結合各富特色的分布式服務,更進一步簡化了分布式環境中程式相互之間的協作,
2.5.2 代碼
1.application.yml檔案
spring:
redis:
host: 47.94.93.93
port: 6379
password: 123456
database: 2
2.api
@RestController
@Slf4j
public class RedissonLockController {
@Autowired
private RedissonClient redissonClient;
@RequestMapping("redissonLock")
public String redissonLock() {
RLock rLock = redissonClient.getLock("order");
try {
rLock.lock(30, TimeUnit.SECONDS);
log.info("我獲得了鎖");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.info("我釋放了鎖");
rLock.unlock();
}
log.info("方法執行完成");
return "方法執行完成";
}
}
3.maven
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.12.0</version>
</dependency>
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/294667.html
標籤:其他
上一篇:Hive中的多維分析函式
