小菜鸟

java菜鸟号正在起航

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

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

可见性问题的根源

CPU 缓存架构与内存可见性

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

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

指令重排序与执行乱序

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

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

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

volatile 的核心作用

1. 保证可见性

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

阅读全文 »

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

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

问题原因分析

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

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

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

@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 实现:

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

使用HttpComponentsClientHttpRequestFactory的优势:

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

完整示例

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 依赖:

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3</version>
</dependency>

Gradle:

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 中最常用的操作,用于从表中检索数据,语法灵活且功能强大。

基本语法

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。

代码解析

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)。

代码解析(顺推)

阅读全文 »

Callable接口与异步任务处理:从基础到实战

在 Java 并发编程中,Callable接口与Future框架是处理异步任务的核心组件,它们弥补了Runnable接口在返回值和异常处理上的不足,为多线程任务提供了更灵活的结果获取机制。本文将深入解析CallableFutureFutureTaskCompletionService的设计原理与实战应用。

Callable 接口:超越 Runnable 的异步任务定义

Runnable 的局限性

Runnable是 Java 早期定义线程任务的接口,但其run()方法存在两大缺陷:

  • 无返回值:无法直接获取任务执行结果,需通过共享变量间接传递,代码繁琐;
  • 无异常抛出run()方法声明不允许抛出受检异常,必须在方法内部捕获处理,增加了代码复杂度。

Callable 的改进

Callable接口专为解决上述问题设计,其定义如下:

@FunctionalInterface  
public interface Callable<V> {  
    V call() throws Exception;  
}

核心优势

  • 有返回值call()方法返回泛型V,直接承载任务结果;
  • 支持异常:声明抛出Exception,允许任务将异常传递给调用方处理;
  • 函数式接口:可配合 Lambda 表达式简化代码。

Callable 与 Runnable 的对比

特性 Runnable Callable
方法名 run() call()
返回值 void V(泛型)
异常处理 不允许抛出受检异常 允许抛出Exception
线程启动方式 直接通过Thread启动 需配合FutureTask使用

Future 接口:异步结果的生命周期管理

Callable仅定义任务,而Future接口则负责管理任务的执行过程和结果获取,是 “生产者 - 消费者” 模型中的桥梁。

Future 接口的核心方法

阅读全文 »
0%