面试地点:某互联网大厂的现代化办公区,面试室宽敞明亮,面试官坐在主位,表情严肃而专注,小兰则坐在对面,自信满满但内心略显紧张。
ConcurrentHashMap
是如何保证线程安全的?面试官:小兰,ConcurrentHashMap
是 Java 中常用的线程安全集合,请简单说说它是如何实现线程安全的?
小兰:嗯,这个我知道!ConcurrentHashMap
是线程安全的,它内部用了一个分段锁(Segment)的设计。每个段其实就是一个小的HashMap
,锁住的是段而不是整个ConcurrentHashMap
,这样可以提高并发性能。比如,当我有多个线程同时操作不同的键时,它们就不会互相等待了。
面试官:很好,那你能不能具体说说ConcurrentHashMap
的分段锁机制是怎么工作的?
小兰:额……分段锁就是把ConcurrentHashMap
分成多个小块,每个小块是一个独立的HashMap
,每个小块有自己的锁。这样当一个线程操作某个键时,只会锁住对应的段,其他段的线程可以继续操作,所以它的并发性能就高了。
面试官:(微微点头)那如果我问你ConcurrentHashMap
的扩容机制,你能解释一下吗?
小兰:额……扩容的话,就是当ConcurrentHashMap
的键值对太多时,它会重新分配空间,把原有的数据重新映射到新的段里。这个过程应该是线程安全的,但具体实现细节我不是很清楚。
面试官:(微微皱眉)好的,我们继续下一道题。
面试官:小兰,假设我们要实现一个简单的用户登录API,你如何用Spring Boot来完成?
小兰:这很简单!首先,我会创建一个Spring Boot项目,然后用@RestController
注解定义一个控制器。比如,我可以写一个UserController
类,里面有一个@PostMapping("/login")
方法来接收登录请求,然后用@RequestBody
接收请求体中的用户名和密码。接着我会调用服务层去验证用户信息,最后返回一个JSON响应。
面试官:那服务层呢?你怎么和数据库交互?
小兰:服务层的话,我会用Spring Data JPA。我可以定义一个UserRepository
接口,继承JpaRepository
,然后用@Query
注解写一些自定义的查询。数据库那边我可能会用MySQL,然后在application.properties
里配置数据库连接信息。
面试官:嗯,那你怎么保证这个登录API的安全性?
小兰:安全性的话,我会用Spring Security。我可以配置一个安全过滤器,对请求进行拦截,然后检查用户是否通过了认证。还可以用JWT生成一个令牌,每次请求都带上这个令牌,服务器会验证令牌的有效性。
面试官:(有些怀疑)好的,我们继续。
面试官:小兰,假设我们需要一个分布式锁来保证某个资源在同一时间只能被一个线程访问,你会怎么设计?
小兰:额……分布式锁的话,我可以用Redis
实现。具体来说,我会用Redis
的SETNX
命令来设置一个键值对,然后设置一个过期时间。如果SETNX
成功,就说明拿到了锁;如果失败,就说明已经有别的线程占着锁了。解锁的时候就直接删除这个键。
面试官:那如果持有锁的线程挂掉了,锁怎么办?
小兰:哦,这个问题我考虑到了!我可以在Redis
里设置一个过期时间,这样即使线程挂掉,锁也会自动释放。
面试官:(点点头)好的,我们继续。
面试官:小兰,假设我们要设计一个电商秒杀系统,其中一个关键点是库存扣减逻辑。你会怎么实现?
小兰:秒杀系统的库存扣减啊,这个我知道!我会用Redis
来存储库存,因为Redis
速度快,适合高并发场景。具体来说,我可以在秒杀开始前把库存存到Redis
里,然后用SETNX
保证只有一个线程能扣减库存。等库存扣减完成后,再更新数据库。
面试官:那你为什么要用Redis
而不是直接操作数据库?
小兰:因为数据库操作太慢了,秒杀的时候并发量很大,直接操作数据库会挂的。Redis
速度快,适合这种高并发场景。
面试官:那如果Redis
的库存扣减完了,但数据库更新失败了怎么办?
小兰:(思考片刻)额……这个……我可能没想清楚。我觉得可以用事务,或者在更新数据库失败的时候重试几次?
面试官:(微微摇头)好的,我们继续。
面试官:小兰,假设我们要实现一个活动页,用户只能点击一次领取奖励。你如何防止用户频繁刷新页面?
小兰:防刷机制的话,我觉得可以用Redis
的计数器。每个用户点击领取奖励时,我在Redis
里记录一个计数器,比如user:clicks:userId
,如果计数器超过1就提示用户已经领取过了。
面试官:那如果用户频繁刷新页面,Redis
的计数器会不会被暴力刷爆?
小兰:(慌张)额……这个……我觉得可以用Redis
的过期时间,比如设置每个用户1分钟只能点击一次。如果用户刷得太多,我就封禁他的IP?
面试官:(皱眉)好的,我们继续。
面试官:小兰,假设我们要设计一个高并发的秒杀系统,每秒有上万用户参与秒杀。你会怎么保证系统的高可用性和性能?
小兰:高并发秒杀系统啊,这个我有思路!首先我会用Redis
来缓存库存,因为Redis
速度快。然后我会用限流来控制并发量,比如用Guava的RateLimiter
。为了提高性能,我还会用分库分表来存储订单数据,这样数据库的压力就不会太大了。
面试官:那你怎么保证秒杀的公平性?比如,先付款的用户优先获得商品?
小兰:额……这个……我觉得可以用Redis
的ZSet
,把用户的支付时间作为分数存进去,然后按分数从小到大排序,分数最小的就是最先付款的用户。
面试官:那如果Redis
挂了怎么办?
小兰:(慌张)额……这个……我觉得可以用Redis
的主从复制,或者用Redis Cluster
来保证高可用性?
面试官:(微微摇头)好的,我们继续。
面试官:今天的面试就到这里,后续有消息HR会通知你。
小兰:好的,谢谢面试官!
ConcurrentHashMap
是如何保证线程安全的?ConcurrentHashMap
是 Java 并发集合中非常重要的一个类,它通过分段锁(Segment)机制实现了线程安全和高并发性能。
正确答案:
分段锁(Segment)机制:
ConcurrentHashMap
内部将整个哈希表分为多个段(Segment),每个段是一个小的 ReentrantLock
锁。这种设计使得多个线程可以同时操作不同段的 HashMap
,从而提高并发性能。锁粒度优化:
HashMap
在多线程环境下需要使用 synchronized
锁住整个集合,导致并发性能差。而 ConcurrentHashMap
的分段锁将锁的粒度缩小到每个段,减少了锁的竞争。并发写操作:
ConcurrentHashMap
在写操作时(如 put
或 remove
)会锁住对应的段,但读操作(如 get
)则是无锁的。读操作通过 volatile
保证可见性,从而避免了锁的开销。扩容机制:
ConcurrentHashMap
的扩容是一个复杂的操作,涉及重新分配哈希表的段,并将旧段的数据重新映射到新段中。扩容过程中,仍然会保证线程安全,但可能会有一定的性能开销。业务痛点:
HashMap
无法满足线程安全需求,而 ConcurrentHashMap
的分段锁机制能够有效提升并发性能,同时保证线程安全。技术选型:
Hashtable
:Hashtable
是线程安全的,但锁住的是整个集合,性能较差。Collections.synchronizedMap
:虽然线程安全,但仍然是锁住整个集合,性能不如 ConcurrentHashMap
。最佳实践:
ConcurrentHashMap
。Spring Boot 是 Java 后端开发中非常流行的框架,用于快速搭建 RESTful API。
正确答案:
创建 Spring Boot 项目:
web
、data-jpa
等)。定义控制器:
@RestController
注解定义控制器,例如: @RestController
public class UserController {
@PostMapping("/login")
public ResponseEntity login(@RequestBody UserLoginRequest request) {
// 验证用户
return ResponseEntity.ok("Login successful");
}
}
服务层与数据库交互:
Spring Data JPA
定义 Repository
接口: public interface UserRepository extends JpaRepository {
Optional findByUsername(String username);
}
Repository
方法: @Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User validateUser(String username, String password) {
Optional user = userRepository.findByUsername(username);
if (user.isPresent() && user.get().checkPassword(password)) {
return user.get();
}
return null;
}
}
安全性:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@Component
public class JwtTokenUtil {
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(SignatureAlgorithm.HS512, "secretkey")
.compact();
}
}
业务痛点:
技术选型:
最佳实践:
分布式锁是分布式系统中常见的需求,用于保证资源的互斥访问。
正确答案:
基于 Redis 的分布式锁:
SETNX
命令设置唯一键值对,成功则获取锁,失败则说明已有锁。EXPIRE
),避免锁持有者挂掉导致死锁。String lockKey = "distributed_lock";
String identifier = UUID.randomUUID().toString();
Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, identifier, 10, TimeUnit.SECONDS);
if (acquired) {
try {
// 操作资源
} finally {
// 释放锁
String value = redisTemplate.opsForValue().get(lockKey);
if (value != null && value.equals(identifier)) {
redisTemplate.delete(lockKey);
}
}
}
锁的释放:
Lua
脚本来保证锁的原子性释放,避免误删其他线程的锁。String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), identifier);
业务痛点:
技术选型:
最佳实践:
Redis
的 SETNX
和 EXPIRE
实现分布式锁。Lua
脚本保证锁的原子性释放。秒杀系统是典型的高并发场景,库存扣减是核心功能之一。
正确答案:
库存预热:
Redis
,利用 Redis
的高并发性能。库存扣减逻辑:
Redis
的 DECR
命令扣减库存,保证原子性。例如: Long remaining = redisTemplate.decr("inventory:productId");
if (remaining >= 0) {
// 扣减成功
} else {
// 库存不足
}
库存同步:
Redis
中的库存数据同步回数据库,确保数据一致性。分布式事务:
业务痛点:
技术选型:
Redis
的高性能适合高并发场景。最佳实践:
Redis
缓存库存,结合 DECR
命令实现原子扣减。活动页防刷是常见的安全需求,防止用户频繁刷新页面。
正确答案:
基于 Redis 的计数器:
Redis
的 INCR
命令记录用户的操作次数,例如: String key = "user:clicks:" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count > 1) {
// 防刷逻辑,提示用户已领取
}
设置过期时间:
EXPIRE
设置计数器的过期时间,避免永久性限制: redisTemplate.expire(key, 60, TimeUnit.SECONDS);
IP 限制:
结合验证码:
业务痛点:
技术选型:
Redis
的计数器更灵活,可以按用户维度限制。最佳实践:
Redis
计数器记录用户操作次数,结合过期时间。高并发秒杀系统需要考虑性能、公平性和高可用性。
正确答案:
RateLimiter
或 Sentinel 实现限流,控制秒杀参与人数。RateLimiter limiter = RateLimiter.create(100); // 每秒允许100个请求
if (lim