简介:
ReadWriteLock(如ReentrantReadWriteLock):
允许多个读线程同时访问共享资源,但写操作是排他的。
这种机制在读操作远多于写操作的场景下可以显著提高并发性能。
读锁(Read Lock): 多个线程可以同时获得读锁。 读锁在没有写锁的情况下可以被多个线程持有。 读操作不会修改共享资源,因此可以同时进行,提高了并发性能。
写锁(Write Lock): 写锁是独占的,一次只能被一个线程持有。 当某个线程持有写锁时,其他线程无法获取读锁或写锁,从而确保了独占性。 写锁用于对共享资源进行修改或更新的操作。
话不多说上代码示例:
package com.shop.cyshop.commons.locker;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* LockService
*
* @version: 1.0
*/
@Slf4j
@Component
@AllArgsConstructor
public class LockService2 {
private RedissonClient redissonClient;
/**
* 获取分布式锁
*
* @param key key
* @param username 操作人姓名
* @param supplier
* @param <R>
* @return
*/
public <R> R lock(String key, String username, Supplier<R> supplier) {
log.info("LockService lock 加锁信息: key:{}, username:{}", key, username);
return this.lockInner(key, supplier, username);
}
/**
* 支持自定义提示的分布式锁
*
* @param key key
* @param supplier function
* @param message 自定义提示
* @param <R>
* @return
*/
public <R> R lockAddErrorTitle(String key, Supplier<R> supplier, String... message) {
log.info("LockService lockAddErrorTitle 加锁信息: key:{}, message:{}", key, Objects.nonNull(message) ? message.toString() : "null");
return this.lockInner(key, supplier, null, message);
}
private <R> R lockInner(String key, Supplier<R> supplier, String username, String... message) {
if (StringUtils.isBlank(key)) {
return supplier.get();
}
key = String.format("{%s}::", "global-lock") + key;
RLock clientLock = redissonClient.getLock(key);
try {
if (clientLock.tryLock()) {
return supplier.get();
} else {
if (Objects.nonNull(message) && message.length > 1) {
throw new RuntimeException("操作异常:" + message.toString());
}
throw new RuntimeException("该业务" + username + "正在操作中,稍后重试即可.");
}
} finally {
if (clientLock.isHeldByCurrentThread()) {
clientLock.unlock();
}
}
}
public <R> R lock(String prefix, String key, Supplier<R> supplier, Integer wait, Integer lease, TimeUnit unit) {
key = String.format("{%s}::", prefix) + StrUtil.format("global-lock::{}", key);
if (StringUtils.isBlank(key)) {
return supplier.get();
}
RLock clientLock = redissonClient.getLock(key);
try {
if (clientLock.tryLock(wait, lease, unit)) {
log.info("获取到执行锁:{}", key);
return supplier.get();
} else {
throw new RuntimeException("该业务正在操作中,稍后重试即可.");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (clientLock.isHeldByCurrentThread()) {
clientLock.unlock();
log.info("释放执行锁:{}", key);
}
}
}
/**
* 读锁(Read Lock):
* 多个线程可以同时获得读锁。
* 读锁在没有写锁的情况下可以被多个线程持有。
* 读操作不会修改共享资源,因此可以同时进行,提高了并发性能。
*
* @param prefix
* @param key
* @param supplier
* @param wait
* @param lease
* @param unit
* @param <R>
* @return
*/
public <R> R readLock(String prefix, String key, Supplier<R> supplier, Integer wait, Integer lease, TimeUnit unit) {
key = String.format("{%s}::", prefix) + StrUtil.format("global-rw-lock::{}", key);
if (StringUtils.isBlank(key)) {
return supplier.get();
}
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
RLock rLock = readWriteLock.readLock();
try {
if (rLock.tryLock(wait, lease, unit)) {
log.info("获取到执行锁:{}", key);
return supplier.get();
} else {
throw new RuntimeException("该业务正在操作中,稍后重试即可.");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
log.info("释放执行锁:{}", key);
}
}
}
/**
* 写锁(Write Lock):
* 写锁是独占的,一次只能被一个线程持有。
* 当某个线程持有写锁时,其他线程无法获取读锁或写锁,从而确保了独占性。
* 写锁用于对共享资源进行修改或更新的操作。
*
* @param prefix
* @param key
* @param supplier
* @param wait
* @param lease
* @param unit
* @param <R>
* @return
*/
public <R> R writeLock(String prefix, String key, Supplier<R> supplier, Integer wait, Integer lease, TimeUnit unit) {
key = String.format("{%s}::", prefix) + StrUtil.format("global-lock::{}", key);
if (StringUtils.isBlank(key)) {
return supplier.get();
}
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
RLock rLock = readWriteLock.writeLock();
try {
if (rLock.tryLock(wait, lease, unit)) {
log.info("获取到执行锁:{}", key);
return supplier.get();
} else {
throw new RuntimeException("该业务正在操作中,稍后重试即可.");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
log.info("释放执行锁:{}", key);
}
}
}
}
实际使用示例:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.shop.cyshop.commons.config.RedisService;
import com.shop.cyshop.commons.locker.LockService;
import com.shop.cyshop.commons.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "CommonWechatUtil")
@Component
public class CommonWechatUtil implements ApplicationListener<ApplicationStartedEvent>, ApplicationContextAware {
private static ApplicationContext applicationContext = null;
private static RedisService redisServices;
private static LockService lockService;
public static final String WE_CHAT_CODE_ERROR = "获取小程序分享码失败";
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
CommonWechatUtil2.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
redisServices = applicationContext.getBean(RedisService.class);
lockService = applicationContext.getBean(LockService.class);
log.info("Bean 启动加载完毕,{},{}", redisServices, lockService);
}
/**
* 测试环境小程序密钥
*/
public static String getTestWeChatCode(String appId, String secret) {
try {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
log.info("进入B端获取access_token入参 appId:{},secret:{}", appId, secret);
//读逻辑
String access_token = lockService.readLock("sys", "getTestWeChatCode-readLock", () -> {
Object code = redisServices.getCacheObject("getTestWeChatCode-token");
if (null != code) {
return String.valueOf(code);
}
return null;
}, 2, 2, TimeUnit.SECONDS);
if (StringUtils.isNotBlank(access_token)) {
return access_token;
}
//写逻辑
access_token = lockService.writeLock("sys", "getTestWeChatCode-readWriteLock", () -> {
HttpGet httpGet = new HttpGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + secret);
try {
HttpResponse response = httpClient.execute(httpGet);
if (null != response) {
String resp_body = EntityUtils.toString(response.getEntity());
log.info("微信返回信息:resp_body = {}", resp_body);
JSONObject jsonObject = JSON.parseObject(resp_body);
String accessToken = jsonObject.getString("access_token");
if (StringUtils.isNotBlank(accessToken)) {
log.info("token获取成功" + accessToken);
redisServices.setCacheObject("getTestWeChatCode-token", accessToken, 6000L, TimeUnit.SECONDS);
return accessToken;
} else {
log.error("获取小程序分享码access_token为空");
throw new RuntimeException(WE_CHAT_CODE_ERROR);
}
} else {
log.error("获取小程序分享码access_token为空");
throw new RuntimeException(WE_CHAT_CODE_ERROR);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}, 2, 2, TimeUnit.SECONDS);
return access_token;
} catch (Exception e) {
log.error("获取小程序分享码access_token失败", e);
throw new RuntimeException(WE_CHAT_CODE_ERROR);
}
}
}
redis工具类:
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* spring redis 工具类
*
**/
@Component
@Data
@Slf4j
public class RedisService {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
}