0%

synchronized关键字:Java 中的同步机制与锁升级原理

synchronized是 Java 中用于保证线程安全的核心关键字,通过实现同步互斥来避免多线程并发访问共享资源时的竞态条件。随着 JVM 的演进,synchronized从早期的 “重量级锁” 发展为具备锁升级能力的高效同步机制。本文将深入解析其作用、实现原理及锁升级过程。

synchronized 的核心作用

synchronized的核心目标是解决多线程并发问题,具体通过以下特性实现:

  1. 原子性:保证临界区代码(被synchronized修饰的代码块或方法)同一时刻只能被一个线程执行,确保操作不可分割;
  2. 可见性:通过 JMM(Java 内存模型)的规则,确保一个线程对共享变量的修改能被其他线程立即看到;
  3. 有序性:禁止指令重排序,保证临界区代码按顺序执行。

synchronized 的使用方式

synchronized可修饰代码块或方法,其锁对象的选择决定了同步粒度:

修饰实例方法

锁对象为当前实例(this),同一实例的多个synchronized方法共享同一把锁。

1
2
3
public synchronized void method() {  
// 临界区代码
}

修饰静态方法

锁对象为当前类的 Class 对象(如MyClass.class),所有静态synchronized方法共享同一把锁。

阅读全文 »

volatile关键字:Java 内存可见性的实现与应用

在多线程编程中,volatile是一个重要的关键字,用于保证共享变量的可见性禁止指令重排序。本文将深入解析volatile的作用、原理及最佳实践。

可见性问题的根源

CPU 缓存架构与内存可见性

现代 CPU 为提高数据访问速度,引入多级缓存(L1、L2、L3)。当线程访问共享变量时:

  • 变量值会从主内存拷贝到 CPU 缓存;
  • 线程修改变量后,新值可能暂存于缓存,未及时刷新到主内存;
  • 其他线程的 CPU 缓存中可能仍保留旧值,导致可见性问题

指令重排序与执行乱序

编译器和 CPU 为优化性能,可能对指令进行重排序,只要不影响单线程执行结果。但在多线程环境中,重排序可能导致逻辑错误,例如:

1
2
3
4
5
6
7
// 线程A  
instance = new Singleton(); // 可能被重排序为:分配内存 → 设置引用 → 初始化对象

// 线程B
if (instance != null) { // 此时instance已指向内存,但对象可能未初始化
instance.use(); // 可能导致NPE
}

volatile 的核心作用

1. 保证可见性

volatile修饰的变量具有以下特性:

阅读全文 »

RestTemplate 401 异常解析失败问题解决方案

当使用 RestTemplate 默认构造器时,处理 401 等错误响应时常常会遇到无法正确获取响应体的问题。这是因为默认的请求工厂实现存在一定的局限性。

问题原因分析

RestTemplate 默认使用SimpleClientHttpRequestFactory,它基于 JDK 的HttpURLConnection实现。当遇到 401 等错误状态码时:

1
2
3
if (response.getStatusCode().isError()) { // 4XX或者5XX
InputStream inputStream = response.getBody();
}

SimpleClientHttpResponsegetBody()方法实现存在问题:

1
2
3
4
5
6
7
@Override
public InputStream getBody() throws IOException {
// 当响应码为401时,getErrorStream()可能返回null
InputStream errorStream = this.connection.getErrorStream();
this.responseStream = (errorStream != null ? errorStream : this.connection.getInputStream());
return this.responseStream;
}

此时调用getInputStream()会抛出IOException,导致无法获取错误响应体。这是因为 JDK 的HttpURLConnection在处理 401 等认证错误时,不会将响应体通过getErrorStream()返回,而是直接抛出异常。

解决方案

解决这个问题的方法是更换 RestTemplate 的请求工厂,使用 Apache 的 HttpClient 实现:

1
2
// 添加Apache HttpClient依赖后
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

使用HttpComponentsClientHttpRequestFactory的优势:

  1. 能正确处理 401 等错误响应的响应体
  2. 提供更丰富的 HTTP 特性支持
  3. 更好的连接池管理
  4. 更灵活的超时设置

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate() {
// 使用HttpComponentsClientHttpRequestFactory替代默认工厂
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

// 可以在这里配置超时时间
requestFactory.setConnectTimeout(5000);
requestFactory.setReadTimeout(5000);

return new RestTemplate(requestFactory);
}

// 使用示例
public void useRestTemplate() {
RestTemplate restTemplate = restTemplate();
try {
// 执行请求
org.springframework.http.ResponseEntity<String> response =
restTemplate.getForEntity("https://api.example.com/protected-resource", String.class);

if (response.getStatusCode().is2xxSuccessful()) {
// 处理成功响应
String body = response.getBody();
} else {
// 处理错误响应,此时可以正常获取响应体
String errorBody = response.getBody();
}
} catch (org.springframework.web.client.HttpStatusCodeException e) {
// 捕获异常并获取错误信息
String errorBody = e.getResponseBodyAsString();
int statusCode = e.getRawStatusCode();
// 处理异常
}
}
}

依赖配置

使用HttpComponentsClientHttpRequestFactory需要添加 Apache HttpClient 依赖:

1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>

Gradle:

1
implementation 'org.apache.httpcomponents.client5:httpclient5:5.3'

总结

通过替换 RestTemplate 的请求工厂为HttpComponentsClientHttpRequestFactory,可以解决 401 等错误响应解析失败的问题,同时获得更强大的 HTTP 客户端功能。这是在生产环境中使用 RestTemplate 的推荐配置方式

SQL 核心操作语法详解:查询、插入、删除与修改

SQL(Structured Query Language)是操作关系型数据库的标准语言,核心操作包括数据查询(SELECT)、插入(INSERT)、删除(DELETE/TRUNCATE)和修改(UPDATE)。本文详细解析这些操作的语法规则、使用场景及注意事项,帮助你快速掌握 SQL 基础操作。

查询操作(SELECT)

SELECT 是 SQL 中最常用的操作,用于从表中检索数据,语法灵活且功能强大。

基本语法

1
2
3
4
5
6
SELECT <字段列表>
FROM <表名>
[WHERE <查询条件>]
[GROUP BY <分组字段>]
[HAVING <分组过滤条件>]
[ORDER BY <排序字段> [ASC|DESC]];
各子句说明:
  • SELECT <字段列表>:指定要查询的字段,* 表示查询所有字段(不推荐,效率低且依赖表结构)。
    示例:SELECT id, name FROM users;(查询 users 表的 idname 字段)。
  • FROM <表名>:指定数据来源的表(可多表联合查询,用 JOIN 连接)。
  • WHERE <查询条件>:过滤行数据,支持比较运算符(=, >, <, >=, <=, !=)、逻辑运算符(AND, OR, NOT)、模糊查询(LIKE)等。
    示例:SELECT * FROM orders WHERE amount > 1000 AND status = 'paid';(查询金额 > 1000 且已支付的订单)。
  • GROUP BY <分组字段>:按指定字段分组,通常与聚合函数(COUNT, SUM, AVG, MAX, MIN)配合使用。
    示例:SELECT user_id, COUNT(*) AS order_count FROM orders GROUP BY user_id;(按用户 ID 分组,统计每个用户的订单数)。
  • HAVING <分组过滤条件>:对分组后的结果进行过滤(WHERE 用于分组前过滤,HAVING 用于分组后过滤)。
    示例:SELECT user_id, COUNT(*) AS order_count FROM orders GROUP BY user_id HAVING order_count >= 5;(筛选出订单数≥5 的用户)。
  • ORDER BY <排序字段>:按指定字段排序,ASC 升序(默认),DESC 降序。
    示例:SELECT * FROM products ORDER BY price DESC;(按价格降序排列商品)。

常用查询技巧

  • 去重查询:用 DISTINCT 去除重复行。
    示例:SELECT DISTINCT category FROM products;(查询所有不重复的商品分类)。
  • 限制结果数量:用 LIMIT(MySQL)或 TOP(SQL Server)限制返回行数。
    示例:SELECT * FROM articles LIMIT 10;(查询前 10 篇文章)。
  • 模糊查询:用 LIKE 匹配字符串,% 表示任意字符(包括空),_ 表示单个字符。
    示例:SELECT * FROM users WHERE name LIKE '张%';(查询姓张的用户)。

插入操作(INSERT)

INSERT 用于向表中添加新数据,支持单行插入、批量插入及从其他表复制数据。

阅读全文 »

算法基本使用:从经典问题看算法思想

算法是解决问题的步骤化策略,不同算法思想适用于不同场景。本文结合具体示例,解析最大公约数、递推、递归、分治、穷举、贪婪、回溯等经典算法的核心思想与实现。

最大公约数(欧几里得算法)

核心思想:利用 “两个数的最大公约数等于其中较小数与两数余数的最大公约数” 这一性质,通过反复取模简化问题,直至余数为 0。

算法步骤

  1. m < n,交换 mn(确保 m ≥ n)。
  2. 计算 m % nm 除以 n 的余数)。
  3. 若余数为 0,则 n 即为最大公约数;否则,令 m = nn = 余数,重复步骤 2。

代码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int maxCommonDisvisor(int m, int n) {
int temp;
// 确保 m ≥ n
if (m < n) {
temp = m;
m = n;
n = temp;
}
while (true) {
if (m % n == 0) { // 余数为0,n是最大公约数
return n;
}
// 迭代:m = n,n = 余数
temp = m % n;
m = n;
n = temp;
}
}

扩展

  • 更相减损术:通过反复用大数减小数(而非取模)求最大公约数,适合无法高效取模的场景(如大数运算)。
  • 最小公倍数:两数乘积除以最大公约数,即 lcm(m, n) = m * n / gcd(m, n)

递推算法

核心思想:从已知条件出发,通过迭代计算中间结果,逐步推导出最终答案(分顺推和逆推)。

经典示例:斐波那契数列

斐波那契数列定义为:f(0)=1f(1)=1f(n)=f(n-1)+f(n-2)n ≥ 2)。

代码解析(顺推)

阅读全文 »