我的重生开发之旅:优化DI容器,git提交规范,AOP处理器,锁与并发安全
前言
我重生了,重生到了五一开始的一天。上一世,我天天摆烂,最后惨遭实习生优化。这一世,我要好好内卷...
今天的目标是继续优化DI容器的功能,尝试兼容AOP,并简单做一个后端联调测试。
----------5.2-----------
今天继续完善DI容器,如果有时间就研究一下多线程。
日程
8点,起的最早的一集。
被类加载器问题卡了一下午,晚上先摸摸🐟,看看git提交的规范
晚上9点,不摸了不摸了
凌晨1点了,被AOP逼疯了。
----------5.2-----------
弄了一早上,可算把AOP弄出来了,下午写写代码分析
晚上快8点了,来看看多线程问题
学习内容
省流:
- 优化DI容器
- 类加载器隔离问题和类加载器桥接
- 关于git提交的一些规范
- 手搓AOP处理器
- 锁与并发安全
1. 优化DI容器
先来看看现在存在的问题:
-
循环依赖处理不足:
- 当前实现只能检测循环依赖,但没有解决它(比如通过三级缓存)。
- 对于构造器注入的循环依赖无法处理。
-
作用域支持有限:
- 只有单例(Singleton)和原型(未标注时)两种作用域。
- 缺少request/session等其他常用作用域。
-
接口映射问题:
- 一个接口只能有一个实现类(第一个遇到的会被保留)。
- 没有处理多个实现的情况(比如通过
@Qualifier
)。
-
初始化顺序问题:
- 没有考虑依赖的初始化顺序。
- 缺少
@PostConstruct
等生命周期回调支持。
-
配置灵活性不足:
- 缺少XML/注解/JavaConfig等多种配置方式。
- 没有环境配置(dev/test/prod)支持。
-
性能问题:
- 每次
getBean
都会反射创建新实例(原型作用域时)。 - 没有缓存反射元数据。
- 每次
-
类型安全:
- 大量使用强制类型转换(cast)。
- 泛型支持不完善。
-
线程安全性:
- 没有考虑并发场景下的线程安全。
- singletonInstances等集合不是并发安全的。
-
异常处理:
- 异常信息不够详细。
- 缺少特定的异常类型。
-
扩展性:
- 没有提供扩展点(如
BeanPostProcessor
)。 - 不支持AOP等高级功能。
- 没有提供扩展点(如
-
资源管理:
- 没有提供销毁钩子或资源清理机制。
- 对于需要close的资源没有特殊处理。
-
测试支持:
- 缺少mock注入等测试支持功能。
-
其他功能缺失:
- 不支持条件化Bean(
@Conditional
)。 - 不支持profile。
- 不支持懒加载(
@Lazy
)。 - 不支持
@Value
等属性注入。
- 不支持条件化Bean(
解决方案:
1)支持多包扫描,并写入配置文件
这个比较简单,就不记录了。
2)预处理依赖
java
// 构建依赖图 查询解析依赖的Bean名称,查询其被@KatAutowired标记的子段,
// 然后通过拓扑排序重排依赖处理顺序
private void buildDependencyGraph() {
// 初始化图和入度
registry.getClassRegistry().keySet().forEach(beanName -> {
dependencyGraph.put(beanName, new ArrayList<>());
inDegree.put(beanName, 0);
});
// 构建依赖关系
registry.getClassRegistry().forEach((beanName, clazz) -> {
// 处理字段依赖
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(KatAutowired.class)) {
String dependencyName = registry.resolveBeanName(field.getType());
dependencyGraph.get(beanName).add(dependencyName);
inDegree.put(dependencyName, inDegree.get(dependencyName) + 1);
}
}
// 处理构造器依赖
Constructor<?> autowiredCtor = findAutowiredConstructor(clazz);
if (autowiredCtor != null) {
for (Class<?> paramType : autowiredCtor.getParameterTypes()) {
String dependencyName = registry.resolveBeanName(paramType);
dependencyGraph.get(beanName).add(dependencyName);
inDegree.put(dependencyName, inDegree.get(dependencyName) + 1);
}
}
});
}
3)AOP支持
关于这一大块内容比较复杂,移步到一个大分支进行分析。
4)并发安全
需要注意的是,Tomcat服务器是实现了多线程的
通过synchronized
互斥锁和ConcurrentHashMap
(内部实现了synchronized
)
在map插入时使用putIfAbsent
而不是put
,因为putIfAbsent
是原子操作,更适合并发环境。
2. 类加载器隔离问题
Java类加载器是Java运行时环境(JRE)的重要组成部分,负责在运行时动态加载Java类到JVM内存中。可以通过继承java.lang.ClassLoader
类并重写findClass()
方法来实现自定义类加载器(这里不展开讲)。
类加载器的隔离原理 :
每个类加载器实例都有独立的命名空间,同一个类被不同类加载器加载会被JVM视为不同的类。
Tomcat等Web容器为每个Web应用创建独立的WebAppClassLoader
:
Common ClassLoader
├── WebApp1 ClassLoader
└── WebApp2 ClassLoader
这也是Tomcat热部署的工作原理:创建新的类加载器加载修改后的类。
在我构建的DI容器中,类路径扫描器使用的是系统类加载器(AppClassLoader
),而Tomcat实现了自己的类加载器,导致了类加载隔离。
问题描述 :
因为Tomcat没有被Bean容器管理,并且@WebServlet("/*")
使得Tomcat对类有自己的实现,与@KatComponent
产生了冲突。因此需要在controller层重写init
方法,手动注入依赖:
java
public void init() throws ServletException {
super.init();
// 从 ServletContext 获取 ContainerFactory
ContainerFactory factory = (ContainerFactory) getServletContext().getAttribute("ContainerFactory");
if (factory == null) {
throw new ServletException("ContainerFactory 未初始化!");
}
try {
factory.injectDependencies(this); // 手动注入依赖
} catch (Exception e) {
throw new RuntimeException(e);
}
}
如果不更改类加载器,factory.injectDependencies(this)
这一步将会注入失败,因为controller层的movieService
是由Tomcat类加载器实例化的,而movieServiceImpl
则是Class.forName(className, false, Thread.currentThread().getContextClassLoader())
时由当前线程的上下文加载器决定的。
解决方法 :
在启动服务器后
java
tomcat.start();
// 获取Tomcat类加载器
ClassLoader tomcatLoader = ctx.getLoader().getClassLoader();
// 设置类加载器
Thread.currentThread().setContextClassLoader(tomcatLoader);
ClassPathScanner.setClassLoader(tomcatLoader);
这里的关键是Thread.currentThread().setContextClassLoader(tomcatLoader)
。为什么?
在Java的类加载机制中,默认情况下,类的加载遵循双亲委派模型 :
类加载器会先委托父类加载器加载,如果父类加载器找不到,才自己加载。而Tomcat的WebAppClassLoader
是AppClassLoader
的子加载器。
Bootstrap ClassLoader (JVM内置)
↓
Extension ClassLoader (JVM内置)
↓
System/App ClassLoader (JVM内置,加载CLASSPATH)
↓
Common ClassLoader (Tomcat)
├── Catalina ClassLoader (Tomcat容器专用)
├── Shared ClassLoader (Web应用共享)
└── WebApp ClassLoader (每个Web应用独立)
通过设置上下文类加载器,DriverManager
使用当前线程的ContextClassLoader
(即WebAppClassLoader
)来打破双亲委派机制的限制,让父加载器(AppClassLoader
)能访问子加载器(WebAppClassLoader
)加载的类。
3. 关于git提交的一些规范
基本格式
<type>(<scope>): <subject>
Footer
常用type:
feat
:新功能(feature)的添加。fix
:修复bug或问题。docs
:仅文档内容的更改(如 README、注释)。style
:代码格式的更改(不影响代码运行的变动,例如空格、制表符、等号等)。refactor
:代码重构(既不添加新功能也不修复bug的代码变动)。test
:添加或修改测试(包括单元测试、集成测试等)。chore
:构建过程或辅助工具的变动(例如更新依赖、修改构建脚本等)。gitignore
用这个。build
:影响项目构建或外部依赖项的更改(例如,更新了package.json
、pom.xml
或build.gradle
文件)。ci
:持续集成配置文件和脚本的更改(例如,更新.travis.yml
、.gitlab-ci.yml
等)。配置文件更改用这个。perf
:性能优化。revert
:撤销之前的提交。merge
:合并分支(通常由Git自动生成)。init
:项目的初始化提交。
scope :
可以是项目中任何可识别的部分,例如文件名、模块名、功能区域等。
影响范围比较大时用模块名,比较小时用类/影响
以下是一些常用的影响scope
值示例:
ui
:用户界面相关的更改。api
:API接口或后端服务的更改。db
:数据库相关的更改,如模式、迁移等。auth
:认证和授权相关的更改。config
:配置文件或设置的更改。deps
或dependencies
:依赖项的更新或更改。tests
:测试代码的更改。cli
:命令行界面相关的更改。docs
:文档内容的更改。build
:构建系统或脚本的更改。ci
:持续集成配置的更改。perf
:性能优化相关的更改。security
:安全性相关的更改。logging
:日志系统的更改。internationalization
或i18n
:国际化和本地化的更改。migration
:数据迁移或系统迁移相关的更改。cleanup
:代码清理,如删除无用代码或重构。refactor
:代码重构,不改变外部行为的内部结构调整。feature
:特定功能模块的更改。bugfix
:特定bug修复的更改。
Footer:
- 关联 Issue:如
Closes #123
或Fixes #456
。 - 破坏性变更:以
BREAKING CHANGE:
开头,描述不兼容的改动。
通常在长期维护的项目中使用。
4. 手搓AOP处理器
先来看看AOP的工作流程:
- 扫描注解
- 注册切面类/方法
- 解析,匹配切面表达式
- 动态代理实现类
1)扫描注解
这里可以集成到我的DI容器注册中。
定义切面类映射:
java
private final Set<Class<?>> aspectClasses = new HashSet<>();
通过路径扫描切面(与Component
的扫描类似):
java
private void scanAspects() {
List<Class<?>> aspectList = basePackages.stream()
.flatMap(pkg -> ClassPathScanner.scanClassesWithAnnotation(pkg, KatAspect.class).stream())
.toList();
aspectList.forEach(aspect -> {
aspectClasses.add(aspect);
registerClass(aspect); // 切面也需要作为普通Bean注册
});
}
2)注册切面类/方法
在BeanBuilder
中,执行依赖图处理后对AOP进行注册:
java
// 处理aop注册
private void processAspects() {
registry.getAspectClasses().forEach(aspectClass -> {
try {
Object aspect = createAspectInstance(aspectClass);
aspectInstances.put(aspectClass, aspect);
aspectProcessor.registerAspect(aspect);
} catch (Exception e) {
throw new RuntimeException("Aspect initialization failed: " + aspectClass.getName(), e);
}
});
}
切面也需要依赖注入:
java
private Object createAspectInstance(Class<?> aspectClass) throws Exception {
Object instance = createRawInstance(aspectClass);
injectFields(instance);
return instance;
}
在调用aspectProcessor.registerAspect(aspect)
方法时,将切面实例返回给AspectProcessor
类,注册到切面映射中:
java
// 缓存切点表达式与对应通知方法的映射
private final Map<String, List<AdviceWrapper>> aspectCache = new ConcurrentHashMap<>();
// 注册切面类
public void registerAspect(Object aspect) {
Class<?> aspectClass = aspect.getClass();
Arrays.stream(aspectClass.getMethods())
.filter(m -> m.isAnnotationPresent(KatAround.class))
.forEach(method -> registerAdvice(aspect, method));
}
// 注册单个通知方法
private void registerAdvice(Object aspect, Method adviceMethod) {
KatAround around = adviceMethod.getAnnotation(KatAround.class);
String pointcut = around.value();
int priority = adviceMethod.isAnnotationPresent(KatOrder.class)
? adviceMethod.getAnnotation(KatOrder.class).value() : 0;
// 编译切点表达式
compiledPatterns.computeIfAbsent(pointcut, this::compilePointcut);
// 注册通知方法并按优先级排序
aspectCache.computeIfAbsent(pointcut, k -> new CopyOnWriteArrayList<>())
.add(new AdviceWrapper(aspect, adviceMethod, priority));
aspectCache.get(pointcut).sort(Comparator.comparingInt(AdviceWrapper::priority));
}
AdviceWrapper
是一个通知包装类,包含了切面实例,通知方法,优先级等信息:
java
// 通知包装类
private record AdviceWrapper(Object aspectInstance, Method adviceMethod, int priority) {
private AdviceWrapper(Object aspectInstance, Method adviceMethod, int priority) {
this.aspectInstance = aspectInstance;
this.adviceMethod = adviceMethod;
this.priority = priority;
this.adviceMethod.setAccessible(true);
}
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
return adviceMethod.invoke(aspectInstance, joinPoint);
}
}
关于切面表达式部分,则是实现了较为复杂的逐字符匹配,来避免直接替换导致的问题:
java
// 编译切点表达式为正则
private Pattern compilePointcut(String expression) {
String regex = convertToRegex(expression);
return Pattern.compile(regex);
}
// 转换切点表达式为正则
private static String convertToRegex(String pointcut) {
// 先处理特殊字符转义(但不处理通配符)
StringBuilder regex = new StringBuilder();
char[] chars = pointcut.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
// 处理..通配符(需要看后续字符)
if (c == '.' && i + 1 < chars.length && chars[i + 1] == '.') {
regex.append("\\..+?"); // 非贪婪匹配
i++; // 跳过下一个点
continue;
}
// 处理普通字符转义
switch (c) {
case '.':
regex.append("\\.");
break;
case '$':
regex.append("\\$");
break;
case '*':
// 临时标记,后面统一处理
regex.append("\u0001");
break;
case '(':
// 参数部分临时标记
regex.append(handleParameters(chars, i));
i = skipParameters(chars, i);
break;
default:
regex.append(c);
}
}
// 最后处理*通配符(避免被前面替换影响)
String result = regex.toString().replace("\u0001", "[^.]+");
return "^" + result + "$";
}
// 处理参数部分
private static String handleParameters(char[] chars, int start) {
int end = findParameterEnd(chars, start);
String params = new String(chars, start, end - start + 1);
return switch (params) {
case "(..)" -> "\\(.*\\)";
case "()" -> "\\(\\)";
case "(*)" -> "\\([^,]+\\)";
default -> Pattern.quote(params); // 其他参数形式原样匹配
};
}
skipParameters
和findParameterEnd
用于处理嵌套括号。
转换规则:
切点语法 | 正则表达式 | 说明 |
---|---|---|
. |
\\. |
转义包分隔符 |
.. |
\\..+? |
匹配任意子包(非贪婪) |
* |
[^.]+ |
匹配非点字符(不跨越包层级) |
(..) |
\\(.*\\) |
匹配任意参数 |
() |
\\(\\) |
匹配无参数方法 |
(*) |
\\([^,]+\\) |
匹配单个任意类型参数 |
其他参数形式 | 原样保留 | 如 (String,int) |
示例转换过程 :
输入切点:
com.example..service.*.*(..)
转换步骤:
..
→\\..+?
.
→\\.
*
→\u0001
(临时)(..)
→\\(.*\\)
- 替换
\u0001
→[^.]+
- 添加边界符
最终正则:
^com\.example\..+?service\.[^.]+\.[^.]+\(.*\)$
3)解析,匹配切面表达式
在BeanBuilder
方法中,在创建实例这一步加入对AOP代理的判断:
java
// 创建实例
private Object createInstance(Class<?> clazz) throws Exception {
// 1. 创建原始实例(不区分单例/原型)
Object rawInstance;
Constructor<?> autowiredCtor = findAutowiredConstructor(clazz);
if (autowiredCtor != null) {
rawInstance = createInstanceWithConstructor(autowiredCtor);
} else {
rawInstance = clazz.getDeclaredConstructor().newInstance();
}
// 2. 注入依赖
injectFields(rawInstance);
// 3. 统一应用AOP代理(无论是否单例)
return wrapWithAopIfNeeded(rawInstance, clazz);
}
// 判断是否要生成代理
private Object wrapWithAopIfNeeded(Object rawInstance, Class<?> targetClass) {
try {
if (shouldProxy(targetClass)) {
return createProxy(rawInstance, targetClass);
}
return rawInstance;
} catch (Exception e) {
throw new RuntimeException("AOP proxy creation failed for " + targetClass.getName(), e);
}
}
private boolean shouldProxy(Class<?> targetClass) {
// 不是切面类 && 有匹配的切面逻辑
return !targetClass.isAnnotationPresent(KatAspect.class) &&
aspectProcessor.hasMatchingAdvice(targetClass);
}
转交给Aspectprocessor
类检查切面匹配:
java
// 检查类是否有匹配的切面
public boolean hasMatchingAdvice(Class<?> targetClass) {
return aspectCache.keySet().stream()
.anyMatch(pointcut -> {
// 构建类名模式:com.example.Service -> com.example.Service.*(..)
String classPattern = targetClass.getName() + ".*(..)";
return matchesPointcut(pointcut, classPattern);
});
}
// 检查方法是否有匹配的切面
public boolean hasMatchingAdvice(Method method) {
String methodSignature = buildMethodSignature(method);
return aspectCache.keySet().stream()
.anyMatch(pointcut -> matchesPointcut(pointcut, methodSignature));
}
// 检查切点是否匹配签名
private boolean matchesPointcut(String pointcut, String signature) {
Pattern pattern = compiledPatterns.get(pointcut);
if (pattern == null) {
pattern = compilePointcut(pointcut);
compiledPatterns.put(pointcut, pattern);
}
return pattern.matcher(signature).matches();
}
切面处理成正则的处理逻辑已经在上面给出。
4)动态代理实现类
如果找到匹配的切面则返回BeanBuilder
类中进行代理对象的创建:
java
private Object createProxy(Object target, Class<?> targetClass) throws Exception {
if (targetClass.isInterface()) { //JDK的代理只能处理接口对象
return Proxy.newProxyInstance(
targetClass.getClassLoader(),
new Class<?>[]{targetClass},
(proxy, method, args) -> aspectProcessor.applyAspects(target, method, args) //调用委托给AspectProcessor
);
} else {
return new ByteBuddy()
.subclass(targetClass) // 创建子类
.method(not(isDeclaredBy(Object.class))) // 排除Object原生方法
.intercept(MethodDelegation.to(new AspectInterceptor(target, aspectProcessor))) // 委托给拦截器
.make()
.load(targetClass.getClassLoader(), ClassLoadingStrategy.Default.INJECTION) // 使用INJECTION策略(新类注入到目标类的ClassLoader中)
.getLoaded()
.getDeclaredConstructor()
.newInstance();
}
}
ByteBuddy是一个字节码增强管理库:
xml
<!-- bytebuddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.15.11</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.15.11</version>
</dependency>
AspectInterceptor 方法拦截器
通过方法拦截器,将调用委托给AspectProcessor
:
java
public class AspectInterceptor {
private final Object target;
private final AspectProcessor aspectProcessor;
public AspectInterceptor(Object target, AspectProcessor aspectProcessor) {
this.target = target;
this.aspectProcessor = aspectProcessor;
}
//在运行时,拦截目标方法,并执行切面逻辑
@RuntimeType
public Object intercept(@Origin Method method,
@AllArguments Object[] args,
@SuperCall Callable<?> callable) throws Exception {
try {
return aspectProcessor.applyAspects(target, method, args);
} catch (Throwable throwable) {
if (throwable instanceof Exception) {
throw (Exception) throwable;
}
throw new Exception(throwable);
}
}
}
回到AspectInterceptor
类,执行委托的切面逻辑:
java
// 执行切面逻辑
public Object applyAspects(Object target, Method method, Object[] args) throws Throwable {
// 构建更精确的方法签名
String methodSignature = target.getClass().getName() + "." + method.getName() +
"(" + Arrays.stream(method.getParameterTypes())
.map(Class::getName)
.collect(Collectors.joining(",")) + ")";
// 查找匹配的切面
List<AdviceWrapper> matchedAdvices = aspectCache.entrySet().stream()
.filter(entry -> matchesPointcut(entry.getKey(), methodSignature))
.flatMap(entry -> entry.getValue().stream())
.sorted(Comparator.comparingInt(AdviceWrapper::priority))
.toList();
if (matchedAdvices.isEmpty()) {
return method.invoke(target, args);
}
// 创建连接点
ProceedingJoinPoint joinPoint = new ProceedingJoinPoint(target, method, args);
// 执行通知链
Object result = null;
for (AdviceWrapper advice : matchedAdvices) {
result = advice.invoke(joinPoint);
}
return result;
}
通知链执行流程:
applyAspects ProceedingJoinPoint AdviceWrapper Target 创建(目标,方法,参数) invoke(joinPoint) 执行切面逻辑 proceed() alt [需要继续] loop [通知链] 返回最终结果 applyAspects ProceedingJoinPoint AdviceWrapper Target
典型场景示例
场景 :执行 userService.save(user)
方法
-
签名生成
→
"com.service.UserService.save(com.entity.User)"
-
切面匹配
- 匹配
@Around("com.service..*.save*(..)")
- 匹配
@Before("execution(* save(*))")
- 匹配
-
执行顺序
text1. @Before 切面 2. @Around 切面(内部调用 proceed()) 3. 实际执行 save() 方法 4. @Around 剩余逻辑 5. @AfterReturning 切面
连接点joinPoint
,是连接通知和代理实例的桥梁:
java
public record ProceedingJoinPoint(Object target, Method method, Object[] args) {
public Object proceed() throws Exception {
return method.invoke(target, args);
}
}
注意 :每个切面都收到同一个joinPoint
对象,用于记录当前的进程状态。通过以下的示例,可以更好地理解通知链的执行流程:
调用者 代理 切面1 切面2 切面3 目标方法 joinPoint 调用方法() invoke(joinPoint) joinPoint状态: currentPosition=0 proceed() invoke(joinPoint) joinPoint状态: currentPosition=1 proceed() invoke(joinPoint) joinPoint状态: currentPosition=2 proceed() 反射调用() 结果 返回 返回 返回 返回 返回 返回 最终结果 调用者 代理 切面1 切面2 切面3 目标方法 joinPoint
5. 锁与并发安全
基本的线程锁:
-
互斥锁 (synchronized)
最基础的并发控制机制,确保同一时间只有一个线程能访问共享资源。
javaprivate final Object lock = new Object(); public void safeMethod() { synchronized(lock) { // 临界区代码 } }
-
读写锁 (ReadWriteLock)
允许多个线程同时读,只允许一个线程写,且写时不能读。
javaReadWriteLock rwLock = new ReentrantReadWriteLock(); public void readData() { rwLock.readLock().lock(); try { // 读取操作 } finally { rwLock.readLock().unlock(); } } public void writeData() { rwLock.writeLock().lock(); try { // 写入操作 } finally { rwLock.writeLock().unlock(); } }
基本的并发容器:
ConcurrentHashMap
CopyOnWriteArrayList
BlockingQueue
锁粒度 :
指锁的作用范围大小,合理选择锁粒度能平衡线程安全与系统性能。
-
粗粒度锁
java// 锁整个对象 public synchronized void process() { // 所有操作都在锁内 } // 锁整个集合 synchronized(map) { // 操作map的所有方法 }
-
细粒度锁
java// 为每个数据项单独加锁 public class FineGrainedCache { private final Map<Key, Lock> keyLocks = new HashMap<>(); private final Map<Key, Value> cache = new HashMap<>(); public void update(Key key, Value value) { Lock keyLock; synchronized(this) { keyLock = keyLocks.computeIfAbsent(key, k -> new ReentrantLock()); } keyLock.lock(); try { // 只锁定当前key的操作 cache.put(key, value); } finally { keyLock.unlock(); } } }
应当使用细粒度锁的场景:
- 临界区包含I/O或耗时计算
- 高并发访问
- 资源可自然分片(如用户ID、订单ID等)
- 需要最大化吞吐量
常见优化模式:
-
锁分解:将一个大锁拆分为多个小锁
java// 优化前 public class Account { private final Object lock = new Object(); private long balance; private int transactionCount; public void transfer(Account to, long amount) { synchronized(lock) { this.balance -= amount; to.balance += amount; this.transactionCount++; } } } // 优化后 public class Account { private final Object balanceLock = new Object(); private final Object countLock = new Object(); private long balance; private int transactionCount; public void transfer(Account to, long amount) { synchronized(balanceLock) { this.balance -= amount; to.balance += amount; } synchronized(countLock) { this.transactionCount++; } } }
-
锁分段 :
单个粗粒度锁拆分为多个细粒度锁,通过哈希算法将数据映射到对应的锁。只有操作同一分段的线程才会竞争同一把锁。
javapublic class StripedDictionary<K, V> { private final int NUM_LOCKS = 16; // 分段数 private final Map<K, V> map = new HashMap<>(); private final Object[] locks = new Object[NUM_LOCKS]; // 锁数组 public StripedDictionary() { for (int i = 0; i < NUM_LOCKS; i++) { locks[i] = new Object(); // 初始化所有锁 } } // 通过key的哈希决定使用哪个锁分段 private int getLockIndex(K key) { return Math.abs(key.hashCode() % NUM_LOCKS); } public void put(K key, V value) { int lockIndex = getLockIndex(key); synchronized (locks[lockIndex]) { // 只锁定当前分段 map.put(key, value); } } public V get(K key) { int lockIndex = getLockIndex(key); synchronized (locks[lockIndex]) { // 只锁定当前分段 return map.get(key); } } }
-
读写锁分离 ,例如
ReadWriteLock
的设计模式。
注意:粒度越细的锁,越容易面临复杂编程下的死锁风险。
简单判断是否需要加锁:
否 是 只读 不可变 可变 会修改 是 否 代码段是否涉及共享数据? 无需加锁 数据是否被修改? 检查数据是否不可变 需加锁保证可见性 需加锁保证原子性 是否复合操作? 锁住整个操作序列 选择最小粒度锁
避免死锁风险的技巧:
-
锁顺序固定化
-
尝试锁机制 :尝试获取锁,若失败立即返回,而不会像
synchronized
等传统锁一样阻塞直到成功。线程 锁A 锁B tryLock() tryLock(剩余超时) 执行业务逻辑 unlock() unlock() alt [锁B获取成功] [锁B获取失败] 等待后重试 alt [锁A获取成功] [锁A获取失败] 线程 锁A 锁B
-
超时释放
带超时(500ms) 成功 超时 获取锁A 获取锁B 业务操作 释放锁A
-
死锁检测
是 否 检测线程 扫描所有线程 构建等待图 发现环路? 中断死锁线程 继续监控
注意:实际开发中,要养成对共享资源的访问必须加锁的意识。
结语
大概先做这么多内容,如果还有更多内容,我会放在第二天的blog里。